WIM: Fix handling of reparse points

Reparse point data is not always in the first stream, sometimes it is in
the file data itself. Remove ReparseStream and always put it in the file
data.
This commit is contained in:
John Starks
2016-04-06 13:56:25 -07:00
parent bad3e111b5
commit 3cedae2e26
+94 -69
View File
@@ -153,11 +153,15 @@ const streamentrySize = 38
// ParseError is returned when the WIM cannot be parsed. // ParseError is returned when the WIM cannot be parsed.
type ParseError struct { type ParseError struct {
Oper string Oper string
Path string
Err error Err error
} }
func (e *ParseError) Error() string { func (e *ParseError) Error() string {
return "WIM parse error at " + e.Oper + ": " + e.Err.Error() if e.Path == "" {
return "WIM parse error at " + e.Oper + ": " + e.Err.Error()
}
return fmt.Sprintf("WIM parse error: %s %s: %s", e.Oper, e.Path, e.Err.Error())
} }
// Reader provides functions to read a WIM file. // Reader provides functions to read a WIM file.
@@ -205,7 +209,6 @@ type FileHeader struct {
LinkID int64 LinkID int64
ReparseTag uint32 ReparseTag uint32
ReparseReserved uint32 ReparseReserved uint32
ReparseStream *Stream
} }
// File represents a file or directory in a WIM image. // File represents a file or directory in a WIM image.
@@ -227,7 +230,7 @@ func NewReader(f io.ReaderAt) (*Reader, error) {
} }
if r.hdr.ImageTag != wimImageTag { if r.hdr.ImageTag != wimImageTag {
return nil, &ParseError{"image tag", errors.New("not a WIM file")} return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")}
} }
if r.hdr.Flags&^supportedHdrFlags != 0 { if r.hdr.Flags&^supportedHdrFlags != 0 {
@@ -295,12 +298,12 @@ func (r *Reader) ReadXML() (string, error) {
XMLData := make([]uint16, r.hdr.XMLData.OriginalSize/2) XMLData := make([]uint16, r.hdr.XMLData.OriginalSize/2)
err = binary.Read(rsrc, binary.LittleEndian, XMLData) err = binary.Read(rsrc, binary.LittleEndian, XMLData)
if err != nil { if err != nil {
return "", &ParseError{"XML data", err} return "", &ParseError{Oper: "XML data", Err: err}
} }
// The BOM will always indicate little-endian UTF-16. // The BOM will always indicate little-endian UTF-16.
if XMLData[0] != 0xfeff { if XMLData[0] != 0xfeff {
return "", &ParseError{"XML data", errors.New("invalid BOM")} return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")}
} }
return string(utf16.Decode(XMLData[1:])), nil return string(utf16.Decode(XMLData[1:])), nil
} }
@@ -311,39 +314,39 @@ func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resource
offsetTable, err := r.readResource(res) offsetTable, err := r.readResource(res)
if err != nil { if err != nil {
return nil, nil, &ParseError{"offset table", err} return nil, nil, &ParseError{Oper: "offset table", Err: err}
} }
br := bytes.NewReader(offsetTable) br := bytes.NewReader(offsetTable)
for { for i := 0; ; i++ {
var res streamDescriptor var res streamDescriptor
err := binary.Read(br, binary.LittleEndian, &res) err := binary.Read(br, binary.LittleEndian, &res)
if err == io.EOF { if err == io.EOF {
break break
} }
if err != nil { if err != nil {
return nil, nil, &ParseError{"offset table", err} return nil, nil, &ParseError{Oper: "offset table", Err: err}
} }
if res.Flags()&^supportedResFlags != 0 { if res.Flags()&^supportedResFlags != 0 {
return nil, nil, &ParseError{"offset table", errors.New("unsupported resource flag")} return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")}
} }
// Validation for ad-hoc testing // Validation for ad-hoc testing
if validate { if validate {
sec, err := r.resourceReader(&res.resourceDescriptor) sec, err := r.resourceReader(&res.resourceDescriptor)
if err != nil { if err != nil {
return nil, nil, err panic(fmt.Sprint(i, err))
} }
hash := sha1.New() hash := sha1.New()
_, err = io.Copy(hash, sec) _, err = io.Copy(hash, sec)
sec.Close() sec.Close()
if err != nil { if err != nil {
return nil, nil, err panic(fmt.Sprint(i, err))
} }
var cmphash SHA1Hash var cmphash SHA1Hash
copy(cmphash[:], hash.Sum(nil)) copy(cmphash[:], hash.Sum(nil))
if cmphash != res.Hash { if cmphash != res.Hash {
return nil, nil, errors.New("hash mismatch") panic(fmt.Sprint(i, "hash mismatch"))
} }
} }
@@ -359,7 +362,7 @@ func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resource
} }
if len(images) != int(r.hdr.ImageCount) { if len(images) != int(r.hdr.ImageCount) {
return nil, nil, &ParseError{"offset table", errors.New("mismatched image count")} return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")}
} }
return fileData, images, nil return fileData, images, nil
@@ -369,7 +372,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64,
var secBlock securityblockDisk var secBlock securityblockDisk
err = binary.Read(rsrc, binary.LittleEndian, &secBlock) err = binary.Read(rsrc, binary.LittleEndian, &secBlock)
if err != nil { if err != nil {
err = &ParseError{"security table", err} err = &ParseError{Oper: "security table", Err: err}
return return
} }
@@ -378,7 +381,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64,
secSizes := make([]int64, secBlock.NumEntries) secSizes := make([]int64, secBlock.NumEntries)
err = binary.Read(rsrc, binary.LittleEndian, &secSizes) err = binary.Read(rsrc, binary.LittleEndian, &secSizes)
if err != nil { if err != nil {
err = &ParseError{"security table sizes", err} err = &ParseError{Oper: "security table sizes", Err: err}
return return
} }
@@ -389,7 +392,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64,
sd := make([]byte, size&0xffffffff) sd := make([]byte, size&0xffffffff)
_, err = io.ReadFull(rsrc, sd) _, err = io.ReadFull(rsrc, sd)
if err != nil { if err != nil {
err = &ParseError{"security descriptor", err} err = &ParseError{Oper: "security descriptor", Err: err}
return return
} }
n += int64(len(sd)) n += int64(len(sd))
@@ -398,7 +401,7 @@ func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64,
secsize := int64((secBlock.TotalLength + 7) &^ 7) secsize := int64((secBlock.TotalLength + 7) &^ 7)
if n > secsize { if n > secsize {
err = &ParseError{"security descriptor", errors.New("security descriptor table too small")} err = &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")}
return return
} }
@@ -432,7 +435,7 @@ func (img *Image) Open() (*File, error) {
return nil, err return nil, err
} }
if len(f) != 1 { if len(f) != 1 {
return nil, &ParseError{"root directory", errors.New("expected exactly 1 root directory entry")} return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")}
} }
return f[0], err return f[0], err
} }
@@ -457,7 +460,7 @@ func (img *Image) readdir(rsrc io.Reader) ([]*File, error) {
func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) { func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
lengthBuf, err := r.Peek(8) lengthBuf, err := r.Peek(8)
if err != nil { if err != nil {
return nil, &ParseError{"directory length check", err} return nil, &ParseError{Oper: "directory length check", Err: err}
} }
left := int(binary.LittleEndian.Uint64(lengthBuf)) left := int(binary.LittleEndian.Uint64(lengthBuf))
@@ -466,24 +469,46 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
} }
if left < direntrySize { if left < direntrySize {
return nil, &ParseError{"directory entry", errors.New("size too short")} return nil, &ParseError{Oper: "directory entry", Err: errors.New("size too short")}
} }
var dentry direntry var dentry direntry
err = binary.Read(r, binary.LittleEndian, &dentry) err = binary.Read(r, binary.LittleEndian, &dentry)
if err != nil { if err != nil {
return nil, &ParseError{"directory entry", err} return nil, &ParseError{Oper: "directory entry", Err: err}
} }
left -= direntrySize left -= direntrySize
namesLen := int(dentry.FileNameLength + 2 + dentry.ShortNameLength)
if left < namesLen {
return nil, &ParseError{Oper: "directory entry", Err: errors.New("size too short for names")}
}
names := make([]uint16, namesLen/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, &ParseError{Oper: "file name", Err: err}
}
left -= namesLen
var name, shortName string
if dentry.FileNameLength > 0 {
name = string(utf16.Decode(names[:dentry.FileNameLength/2]))
}
if dentry.ShortNameLength > 0 {
shortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:]))
}
var offset resourceDescriptor var offset resourceDescriptor
zerohash := SHA1Hash{} zerohash := SHA1Hash{}
if dentry.Hash != zerohash { if dentry.Hash != zerohash {
var ok bool var ok bool
offset, ok = img.wim.fileData[dentry.Hash] offset, ok = img.wim.fileData[dentry.Hash]
if !ok { if !ok {
return nil, &ParseError{"directory entry", fmt.Errorf("could not find file data matching hash %v", dentry.Hash)} return nil, &ParseError{Oper: "directory entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %#v", dentry)}
} }
} }
@@ -495,6 +520,8 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
LastWriteTime: dentry.LastWriteTime, LastWriteTime: dentry.LastWriteTime,
Hash: dentry.Hash, Hash: dentry.Hash,
Size: offset.OriginalSize, Size: offset.OriginalSize,
Name: name,
ShortName: shortName,
}, },
offset: offset, offset: offset,
@@ -502,38 +529,28 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
subdirOffset: dentry.SubdirOffset, subdirOffset: dentry.SubdirOffset,
} }
isDir := false
if dentry.Attributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { if dentry.Attributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
f.LinkID = dentry.ReparseHardLink f.LinkID = dentry.ReparseHardLink
if dentry.Attributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
isDir = true
}
} else { } else {
f.ReparseTag = uint32(dentry.ReparseHardLink) f.ReparseTag = uint32(dentry.ReparseHardLink)
f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32) f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32)
} }
if isDir && f.subdirOffset == 0 {
return nil, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")}
} else if !isDir && f.subdirOffset != 0 {
return nil, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")}
}
if dentry.SecurityID != 0xffffffff { if dentry.SecurityID != 0xffffffff {
f.SecurityDescriptor = img.sds[dentry.SecurityID] f.SecurityDescriptor = img.sds[dentry.SecurityID]
} }
namesLen := int(dentry.FileNameLength + 2 + dentry.ShortNameLength)
if left < namesLen {
return nil, &ParseError{"directory entry", errors.New("size too short for names")}
}
names := make([]uint16, namesLen/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, &ParseError{"file name", err}
}
left -= namesLen
if dentry.FileNameLength > 0 {
f.Name = string(utf16.Decode(names[:dentry.FileNameLength/2]))
}
if dentry.ShortNameLength > 0 {
f.ShortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:]))
}
_, err = r.Discard(left) _, err = r.Discard(left)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -546,19 +563,20 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !(s.Name == "" && s.Size == 0) { // The first unnamed stream should be treated as the file stream.
if dentry.Attributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && s.Name == "" { if i == 0 && s.Name == "" {
f.ReparseStream = s f.Hash = s.Hash
} else { f.Size = s.Size
streams = append(streams, s) f.offset = s.offset
} } else if s.Name != "" {
streams = append(streams, s)
} }
} }
f.Streams = streams f.Streams = streams
} }
if dentry.Attributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.ReparseStream == nil { if dentry.Attributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 {
return nil, &ParseError{"directory entry", errors.New("reparse point is missing reparse stream")} return nil, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("reparse point is missing reparse stream")}
} }
return f, nil return f, nil
@@ -567,28 +585,41 @@ func (img *Image) readNextEntry(r *bufio.Reader) (*File, error) {
func (img *Image) readNextStream(r *bufio.Reader) (*Stream, error) { func (img *Image) readNextStream(r *bufio.Reader) (*Stream, error) {
lengthBuf, err := r.Peek(8) lengthBuf, err := r.Peek(8)
if err != nil { if err != nil {
return nil, &ParseError{"stream length check", err} return nil, &ParseError{Oper: "stream length check", Err: err}
} }
left := int(binary.LittleEndian.Uint64(lengthBuf)) left := int(binary.LittleEndian.Uint64(lengthBuf))
if left < streamentrySize { if left < streamentrySize {
return nil, &ParseError{"stream entry", errors.New("size too short")} return nil, &ParseError{Oper: "stream entry", Err: errors.New("size too short")}
} }
var sentry streamentry var sentry streamentry
err = binary.Read(r, binary.LittleEndian, &sentry) err = binary.Read(r, binary.LittleEndian, &sentry)
if err != nil { if err != nil {
return nil, &ParseError{"stream entry", err} return nil, &ParseError{Oper: "stream entry", Err: err}
} }
left -= streamentrySize left -= streamentrySize
if left < int(sentry.NameLength) {
return nil, &ParseError{Oper: "stream entry", Err: errors.New("size too short for name")}
}
names := make([]uint16, sentry.NameLength/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, &ParseError{Oper: "file name", Err: err}
}
left -= int(sentry.NameLength)
name := string(utf16.Decode(names))
var offset resourceDescriptor var offset resourceDescriptor
if sentry.Hash != (SHA1Hash{}) { if sentry.Hash != (SHA1Hash{}) {
var ok bool var ok bool
offset, ok = img.wim.fileData[sentry.Hash] offset, ok = img.wim.fileData[sentry.Hash]
if !ok { if !ok {
return nil, &ParseError{"stream entry", fmt.Errorf("could not find file data matching hash %v", sentry.Hash)} return nil, &ParseError{Oper: "stream entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash)}
} }
} }
@@ -596,24 +627,12 @@ func (img *Image) readNextStream(r *bufio.Reader) (*Stream, error) {
StreamHeader: StreamHeader{ StreamHeader: StreamHeader{
Hash: sentry.Hash, Hash: sentry.Hash,
Size: offset.OriginalSize, Size: offset.OriginalSize,
Name: name,
}, },
wim: img.wim, wim: img.wim,
offset: offset, offset: offset,
} }
if left < int(sentry.NameLength) {
return nil, &ParseError{"stream entry", errors.New("size too short for name")}
}
names := make([]uint16, sentry.NameLength/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, &ParseError{"file name", err}
}
left -= int(sentry.NameLength)
s.Name = string(utf16.Decode(names))
_, err = r.Discard(left) _, err = r.Discard(left)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -634,7 +653,7 @@ func (f *File) Open() (io.ReadCloser, error) {
// Readdir reads the directory entries. // Readdir reads the directory entries.
func (f *File) Readdir() ([]*File, error) { func (f *File) Readdir() ([]*File, error) {
if f.Attributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 { if !f.IsDir() {
return nil, errors.New("not a directory") return nil, errors.New("not a directory")
} }
rsrc, err := f.img.wim.resourceReaderWithOffset(&f.img.offset, f.subdirOffset) rsrc, err := f.img.wim.resourceReaderWithOffset(&f.img.offset, f.subdirOffset)
@@ -644,3 +663,9 @@ func (f *File) Readdir() ([]*File, error) {
defer rsrc.Close() defer rsrc.Close()
return f.img.readdir(rsrc) return f.img.readdir(rsrc)
} }
// IsDir returns whether the given file is a directory. It returns false when it
// is a directory reparse point.
func (f *FileHeader) IsDir() bool {
return f.Attributes&(syscall.FILE_ATTRIBUTE_DIRECTORY|syscall.FILE_ATTRIBUTE_REPARSE_POINT) == syscall.FILE_ATTRIBUTE_DIRECTORY
}