diff options
Diffstat (limited to 'internal')
41 files changed, 642 insertions, 389 deletions
diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index 995a0c8..28a1ca5 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -5,7 +5,6 @@ package configfile import ( "encoding/json" "fmt" - "io/ioutil" "syscall" "os" @@ -183,12 +182,12 @@ func Load(filename string) (*ConfFile, error) { cf.filename = filename // Read from disk - js, err := ioutil.ReadFile(filename) + js, err := os.ReadFile(filename) if err != nil { return nil, err } if len(js) == 0 { - return nil, fmt.Errorf("Config file is empty") + return nil, fmt.Errorf("config file is empty") } // Unmarshal diff --git a/internal/configfile/scrypt.go b/internal/configfile/scrypt.go index 0ce8777..f6201ba 100644 --- a/internal/configfile/scrypt.go +++ b/internal/configfile/scrypt.go @@ -87,19 +87,19 @@ func (s *ScryptKDF) LogN() int { func (s *ScryptKDF) validateParams() error { minN := 1 << scryptMinLogN if s.N < minN { - return fmt.Errorf("Fatal: scryptn below 10 is too low to make sense") + return fmt.Errorf("fatal: scryptn below 10 is too low to make sense") } if s.R < scryptMinR { - return fmt.Errorf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR) + return fmt.Errorf("fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR) } if s.P < scryptMinP { - return fmt.Errorf("Fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP) + return fmt.Errorf("fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP) } if len(s.Salt) < scryptMinSaltLen { - return fmt.Errorf("Fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen) + return fmt.Errorf("fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen) } if s.KeyLen < cryptocore.KeyLen { - return fmt.Errorf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen) + return fmt.Errorf("fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen) } return nil } diff --git a/internal/configfile/validate.go b/internal/configfile/validate.go index ab8917d..5428a7b 100644 --- a/internal/configfile/validate.go +++ b/internal/configfile/validate.go @@ -9,7 +9,7 @@ import ( // Validate that the combination of settings makes sense and is supported func (cf *ConfFile) Validate() error { if cf.Version != contentenc.CurrentVersion { - return fmt.Errorf("Unsupported on-disk format %d", cf.Version) + return fmt.Errorf("unsupported on-disk format %d", cf.Version) } // scrypt params ok? if err := cf.ScryptObject.validateParams(); err != nil { @@ -18,13 +18,13 @@ func (cf *ConfFile) Validate() error { // All feature flags that are in the config file are known? for _, flag := range cf.FeatureFlags { if !isFeatureFlagKnown(flag) { - return fmt.Errorf("Unknown feature flag %q", flag) + return fmt.Errorf("unknown feature flag %q", flag) } } // File content encryption { if cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && cf.IsFeatureFlagSet(FlagAESSIV) { - return fmt.Errorf("Can't have both XChaCha20Poly1305 and AESSIV feature flags") + return fmt.Errorf("can't have both XChaCha20Poly1305 and AESSIV feature flags") } if cf.IsFeatureFlagSet(FlagAESSIV) && !cf.IsFeatureFlagSet(FlagGCMIV128) { diff --git a/internal/contentenc/bpool.go b/internal/contentenc/bpool.go index c4517d3..c62170d 100644 --- a/internal/contentenc/bpool.go +++ b/internal/contentenc/bpool.go @@ -26,6 +26,7 @@ func (b *bPool) Put(s []byte) { if len(s) != b.sliceLen { log.Panicf("wrong len=%d, want=%d", len(s), b.sliceLen) } + //lint:ignore SA6002 We intentionally pass slice by value to avoid allocation overhead in this specific use case b.Pool.Put(s) } diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go index 3005bf5..5bf0b3c 100644 --- a/internal/contentenc/content.go +++ b/internal/contentenc/content.go @@ -151,7 +151,7 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b if len(ciphertext) < be.cryptoCore.IVLen { tlog.Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext)) - return nil, errors.New("Block is too short") + return nil, errors.New("block is too short") } // Extract nonce diff --git a/internal/ctlsocksrv/ctlsock_serve.go b/internal/ctlsocksrv/ctlsock_serve.go index 85f5b65..25d1e44 100644 --- a/internal/ctlsocksrv/ctlsock_serve.go +++ b/internal/ctlsocksrv/ctlsock_serve.go @@ -101,7 +101,7 @@ func (ch *ctlSockHandler) handleRequest(in *ctlsock.RequestStruct, conn *net.Uni } // Neither encryption nor encryption has been requested, makes no sense if in.DecryptPath == "" && in.EncryptPath == "" { - err = errors.New("Empty input") + err = errors.New("empty input") sendResponse(conn, err, "", "") return } @@ -118,7 +118,7 @@ func (ch *ctlSockHandler) handleRequest(in *ctlsock.RequestStruct, conn *net.Uni } // Error out if the canonical path is now empty if clean == "" { - err = errors.New("Empty input after canonicalization") + err = errors.New("empty input after canonicalization") sendResponse(conn, err, "", warnText) return } diff --git a/internal/exitcodes/exitcodes.go b/internal/exitcodes/exitcodes.go index 508ba38..881afda 100644 --- a/internal/exitcodes/exitcodes.go +++ b/internal/exitcodes/exitcodes.go @@ -3,7 +3,7 @@ package exitcodes import ( - "fmt" + "errors" "os" ) @@ -83,7 +83,7 @@ type Err struct { // NewErr returns an error containing "msg" and the exit code "code". func NewErr(msg string, code int) Err { return Err{ - error: fmt.Errorf(msg), + error: errors.New(msg), code: code, } } diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index 0e25de3..bb9e539 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -37,8 +37,6 @@ type File struct { // Every FUSE entrypoint should RLock(). The only user of Lock() is // Release(), which closes the fd and sets "released" to true. fdLock sync.RWMutex - // Content encryption helper - contentEnc *contentenc.ContentEnc // Device and inode number uniquely identify the backing file qIno inomap.QIno // Entry in the open file table @@ -50,6 +48,8 @@ type File struct { lastOpCount uint64 // Parent filesystem rootNode *RootNode + // If this open file is a directory, dirHandle will be set, otherwise it's nil. + dirHandle *DirHandle } // NewFile returns a new go-fuse File instance based on an already-open file @@ -71,7 +71,6 @@ func NewFile(fd int, cName string, rn *RootNode) (f *File, st *syscall.Stat_t, e f = &File{ fd: osFile, - contentEnc: rn.contentEnc, qIno: qi, fileTableEntry: e, rootNode: rn, @@ -175,7 +174,7 @@ func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Er log.Panicf("fileID=%v", fileID) } // Read the backing ciphertext in one go - blocks := f.contentEnc.ExplodePlainRange(off, length) + blocks := f.rootNode.contentEnc.ExplodePlainRange(off, length) alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks) // f.fd.ReadAt takes an int64! if alignedOffset > math.MaxInt64 { @@ -204,10 +203,10 @@ func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Er tlog.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d", alignedOffset, firstBlockNo, alignedLength, n) // Decrypt it - plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID) + plaintext, err := f.rootNode.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID) f.rootNode.contentEnc.CReqPool.Put(ciphertext) if err != nil { - corruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext))) + corruptBlockNo := firstBlockNo + f.rootNode.contentEnc.PlainOffToBlockNo(uint64(len(plaintext))) tlog.Warn.Printf("doRead %d: corrupt block #%d: %v", f.qIno.Ino, corruptBlockNo, err) return nil, syscall.EIO } @@ -285,20 +284,20 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) { } // Handle payload data dataBuf := bytes.NewBuffer(data) - blocks := f.contentEnc.ExplodePlainRange(uint64(off), uint64(len(data))) + blocks := f.rootNode.contentEnc.ExplodePlainRange(uint64(off), uint64(len(data))) toEncrypt := make([][]byte, len(blocks)) for i, b := range blocks { blockData := dataBuf.Next(int(b.Length)) // Incomplete block -> Read-Modify-Write if b.IsPartial() { // Read - oldData, errno := f.doRead(nil, b.BlockPlainOff(), f.contentEnc.PlainBS()) + oldData, errno := f.doRead(nil, b.BlockPlainOff(), f.rootNode.contentEnc.PlainBS()) if errno != 0 { tlog.Warn.Printf("ino%d fh%d: RMW read failed: errno=%d", f.qIno.Ino, f.intFd(), errno) return 0, errno } // Modify - blockData = f.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip)) + blockData = f.rootNode.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip)) tlog.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData)) } tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d", @@ -307,7 +306,7 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) { toEncrypt[i] = blockData } // Encrypt all blocks - ciphertext := f.contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, f.fileTableEntry.ID) + ciphertext := f.rootNode.contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, f.fileTableEntry.ID) // Preallocate so we cannot run out of space in the middle of the write. // This prevents partially written (=corrupt) blocks. var err error @@ -437,7 +436,7 @@ func (f *File) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno { } f.rootNode.inoMap.TranslateStat(&st) a.FromStat(&st) - a.Size = f.contentEnc.CipherSizeToPlainSize(a.Size) + a.Size = f.rootNode.contentEnc.CipherSizeToPlainSize(a.Size) if f.rootNode.args.ForceOwner != nil { a.Owner = *f.rootNode.args.ForceOwner } diff --git a/internal/fusefrontend/file_allocate_truncate.go b/internal/fusefrontend/file_allocate_truncate.go index cae796e..a3decf9 100644 --- a/internal/fusefrontend/file_allocate_truncate.go +++ b/internal/fusefrontend/file_allocate_truncate.go @@ -54,7 +54,7 @@ func (f *File) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) f.fileTableEntry.ContentLock.Lock() defer f.fileTableEntry.ContentLock.Unlock() - blocks := f.contentEnc.ExplodePlainRange(off, sz) + blocks := f.rootNode.contentEnc.ExplodePlainRange(off, sz) firstBlock := blocks[0] lastBlock := blocks[len(blocks)-1] @@ -63,7 +63,7 @@ func (f *File) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) // the file. cipherOff := firstBlock.BlockCipherOff() cipherSz := lastBlock.BlockCipherOff() - cipherOff + - f.contentEnc.BlockOverhead() + lastBlock.Skip + lastBlock.Length + f.rootNode.contentEnc.BlockOverhead() + lastBlock.Skip + lastBlock.Length err := syscallcompat.Fallocate(f.intFd(), FALLOC_FL_KEEP_SIZE, int64(cipherOff), int64(cipherSz)) tlog.Debug.Printf("Allocate off=%d sz=%d mode=%x cipherOff=%d cipherSz=%d\n", off, sz, mode, cipherOff, cipherSz) @@ -113,8 +113,8 @@ func (f *File) truncate(newSize uint64) (errno syscall.Errno) { return fs.ToErrno(err) } - oldB := float32(oldSize) / float32(f.contentEnc.PlainBS()) - newB := float32(newSize) / float32(f.contentEnc.PlainBS()) + oldB := float32(oldSize) / float32(f.rootNode.contentEnc.PlainBS()) + newB := float32(newSize) / float32(f.rootNode.contentEnc.PlainBS()) tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.qIno.Ino, oldB, newB, oldSize, newSize) // File size stays the same - nothing to do @@ -127,9 +127,9 @@ func (f *File) truncate(newSize uint64) (errno syscall.Errno) { } // File shrinks - blockNo := f.contentEnc.PlainOffToBlockNo(newSize) - cipherOff := f.contentEnc.BlockNoToCipherOff(blockNo) - plainOff := f.contentEnc.BlockNoToPlainOff(blockNo) + blockNo := f.rootNode.contentEnc.PlainOffToBlockNo(newSize) + cipherOff := f.rootNode.contentEnc.BlockNoToCipherOff(blockNo) + plainOff := f.rootNode.contentEnc.BlockNoToPlainOff(blockNo) lastBlockLen := newSize - plainOff var data []byte if lastBlockLen > 0 { @@ -161,7 +161,7 @@ func (f *File) statPlainSize() (uint64, error) { return 0, err } cipherSz := uint64(fi.Size()) - plainSz := uint64(f.contentEnc.CipherSizeToPlainSize(cipherSz)) + plainSz := uint64(f.rootNode.contentEnc.CipherSizeToPlainSize(cipherSz)) return plainSz, nil } @@ -174,8 +174,8 @@ func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Er } newEOFOffset := newPlainSz - 1 if oldPlainSz > 0 { - n1 := f.contentEnc.PlainOffToBlockNo(oldPlainSz - 1) - n2 := f.contentEnc.PlainOffToBlockNo(newEOFOffset) + n1 := f.rootNode.contentEnc.PlainOffToBlockNo(oldPlainSz - 1) + n2 := f.rootNode.contentEnc.PlainOffToBlockNo(newEOFOffset) // The file is grown within one block, no need to pad anything. // Write a single zero to the last byte and let doWrite figure out the RMW. if n1 == n2 { @@ -194,7 +194,7 @@ func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Er } // The new size is block-aligned. In this case we can do everything ourselves // and avoid the call to doWrite. - if newPlainSz%f.contentEnc.PlainBS() == 0 { + if newPlainSz%f.rootNode.contentEnc.PlainBS() == 0 { // The file was empty, so it did not have a header. Create one. if oldPlainSz == 0 { id, err := f.createHeader() @@ -203,7 +203,7 @@ func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Er } f.fileTableEntry.ID = id } - cSz := int64(f.contentEnc.PlainSizeToCipherSize(newPlainSz)) + cSz := int64(f.rootNode.contentEnc.PlainSizeToCipherSize(newPlainSz)) err := syscall.Ftruncate(f.intFd(), cSz) if err != nil { tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err) diff --git a/internal/fusefrontend/file_dir_ops.go b/internal/fusefrontend/file_dir_ops.go new file mode 100644 index 0000000..b69e7bc --- /dev/null +++ b/internal/fusefrontend/file_dir_ops.go @@ -0,0 +1,177 @@ +package fusefrontend + +import ( + "context" + "syscall" + + "github.com/hanwen/go-fuse/v2/fs" + "github.com/hanwen/go-fuse/v2/fuse" + "github.com/rfjakob/gocryptfs/v2/internal/configfile" + "github.com/rfjakob/gocryptfs/v2/internal/nametransform" + "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat" + "github.com/rfjakob/gocryptfs/v2/internal/tlog" +) + +func (n *Node) OpendirHandle(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { + var fd int = -1 + var fdDup int = -1 + var file *File + var dirIV []byte + var ds fs.DirStream + rn := n.rootNode() + + dirfd, cName, errno := n.prepareAtSyscallMyself() + if errno != 0 { + return + } + defer syscall.Close(dirfd) + + // Open backing directory + fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) + if err != nil { + errno = fs.ToErrno(err) + return + } + + // NewLoopbackDirStreamFd gets its own fd to untangle Release vs Releasedir + fdDup, err = syscall.Dup(fd) + + if err != nil { + errno = fs.ToErrno(err) + goto err_out + } + + ds, errno = fs.NewLoopbackDirStreamFd(fdDup) + if errno != 0 { + goto err_out + } + + if !rn.args.PlaintextNames { + // Read the DirIV from disk + dirIV, err = rn.nameTransform.ReadDirIVAt(fd) + if err != nil { + tlog.Warn.Printf("OpendirHandle: could not read %s: %v", nametransform.DirIVFilename, err) + errno = syscall.EIO + goto err_out + } + } + + file, _, errno = NewFile(fd, cName, rn) + if errno != 0 { + goto err_out + } + + file.dirHandle = &DirHandle{ + ds: ds, + dirIV: dirIV, + isRootDir: n.IsRoot(), + } + + return file, fuseFlags, errno + +err_out: + if fd >= 0 { + syscall.Close(fd) + } + if fdDup >= 0 { + syscall.Close(fdDup) + } + if errno == 0 { + tlog.Warn.Printf("BUG: OpendirHandle: err_out called with errno == 0") + errno = syscall.EIO + } + return nil, 0, errno +} + +type DirHandle struct { + // Content of gocryptfs.diriv. nil if plaintextnames is used. + dirIV []byte + + isRootDir bool + + // fs.loopbackDirStream with a private dup of the file descriptor + ds fs.FileHandle +} + +var _ = (fs.FileReleasedirer)((*File)(nil)) + +func (f *File) Releasedir(ctx context.Context, flags uint32) { + // Does its own locking + f.dirHandle.ds.(fs.FileReleasedirer).Releasedir(ctx, flags) + // Does its own locking + f.Release(ctx) +} + +var _ = (fs.FileSeekdirer)((*File)(nil)) + +func (f *File) Seekdir(ctx context.Context, off uint64) syscall.Errno { + return f.dirHandle.ds.(fs.FileSeekdirer).Seekdir(ctx, off) +} + +var _ = (fs.FileFsyncdirer)((*File)(nil)) + +func (f *File) Fsyncdir(ctx context.Context, flags uint32) syscall.Errno { + return f.dirHandle.ds.(fs.FileFsyncdirer).Fsyncdir(ctx, flags) +} + +var _ = (fs.FileReaddirenter)((*File)(nil)) + +// This function is symlink-safe through use of openBackingDir() and +// ReadDirIVAt(). +func (f *File) Readdirent(ctx context.Context) (entry *fuse.DirEntry, errno syscall.Errno) { + f.fdLock.RLock() + defer f.fdLock.RUnlock() + + for { + entry, errno = f.dirHandle.ds.(fs.FileReaddirenter).Readdirent(ctx) + if errno != 0 || entry == nil { + return + } + + cName := entry.Name + if cName == "." || cName == ".." { + // We want these as-is + return + } + if f.dirHandle.isRootDir && cName == configfile.ConfDefaultName { + // silently ignore "gocryptfs.conf" in the top level dir + continue + } + if f.rootNode.args.PlaintextNames { + return + } + if !f.rootNode.args.DeterministicNames && cName == nametransform.DirIVFilename { + // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled + continue + } + // Handle long file name + isLong := nametransform.LongNameNone + if f.rootNode.args.LongNames { + isLong = nametransform.NameType(cName) + } + if isLong == nametransform.LongNameContent { + cNameLong, err := nametransform.ReadLongNameAt(f.intFd(), cName) + if err != nil { + tlog.Warn.Printf("Readdirent: incomplete entry %q: Could not read .name: %v", + cName, err) + f.rootNode.reportMitigatedCorruption(cName) + continue + } + cName = cNameLong + } else if isLong == nametransform.LongNameFilename { + // ignore "gocryptfs.longname.*.name" + continue + } + name, err := f.rootNode.nameTransform.DecryptName(cName, f.dirHandle.dirIV) + if err != nil { + tlog.Warn.Printf("Readdirent: could not decrypt entry %q: %v", + cName, err) + f.rootNode.reportMitigatedCorruption(cName) + continue + } + // Override the ciphertext name with the plaintext name but reuse the rest + // of the structure + entry.Name = name + return + } +} diff --git a/internal/fusefrontend/file_holes.go b/internal/fusefrontend/file_holes.go index f35fa70..fc58898 100644 --- a/internal/fusefrontend/file_holes.go +++ b/internal/fusefrontend/file_holes.go @@ -21,12 +21,12 @@ func (f *File) writePadHole(targetOff int64) syscall.Errno { tlog.Warn.Printf("checkAndPadHole: Fstat failed: %v", err) return fs.ToErrno(err) } - plainSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size())) + plainSize := f.rootNode.contentEnc.CipherSizeToPlainSize(uint64(fi.Size())) // Appending a single byte to the file (equivalent to writing to // offset=plainSize) would write to "nextBlock". - nextBlock := f.contentEnc.PlainOffToBlockNo(plainSize) + nextBlock := f.rootNode.contentEnc.PlainOffToBlockNo(plainSize) // targetBlock is the block the user wants to write to. - targetBlock := f.contentEnc.PlainOffToBlockNo(uint64(targetOff)) + targetBlock := f.rootNode.contentEnc.PlainOffToBlockNo(uint64(targetOff)) // The write goes into an existing block or (if the last block was full) // starts a new one directly after the last block. Nothing to do. if targetBlock <= nextBlock { @@ -45,12 +45,12 @@ func (f *File) writePadHole(targetOff int64) syscall.Errno { // Zero-pad the file of size plainSize to the next block boundary. This is a no-op // if the file is already block-aligned. func (f *File) zeroPad(plainSize uint64) syscall.Errno { - lastBlockLen := plainSize % f.contentEnc.PlainBS() + lastBlockLen := plainSize % f.rootNode.contentEnc.PlainBS() if lastBlockLen == 0 { // Already block-aligned return 0 } - missing := f.contentEnc.PlainBS() - lastBlockLen + missing := f.rootNode.contentEnc.PlainBS() - lastBlockLen pad := make([]byte, missing) tlog.Debug.Printf("zeroPad: Writing %d bytes\n", missing) _, errno := f.doWrite(pad, int64(plainSize)) diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go index 687a386..95be48d 100644 --- a/internal/fusefrontend/node.go +++ b/internal/fusefrontend/node.go @@ -71,28 +71,54 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) (errno syscall.Errno) { // If the kernel gives us a file handle, use it. if f != nil { - return f.(fs.FileGetattrer).Getattr(ctx, out) + if fga, ok := f.(fs.FileGetattrer); ok { + return fga.Getattr(ctx, out) + } } + rn := n.rootNode() + var st *syscall.Stat_t + var err error dirfd, cName, errno := n.prepareAtSyscallMyself() + // Hack for deleted fifos. As OPEN on a fifo does not reach + // the filesystem, we have no fd to access it. To make "cat" and git's + // t9300-fast-import.sh happy, we fake it as best as we can. + // https://github.com/rfjakob/gocryptfs/issues/929 + if errno == syscall.ENOENT && n.StableAttr().Mode == syscall.S_IFIFO { + out.Mode = syscall.S_IFIFO + out.Ino = n.StableAttr().Ino + // cat looks at this to determine the optimal io size. Seems to be always 4096 for fifos. + out.Blksize = 4096 + // We don't know what the owner was. Set it to nobody which seems safer + // than leaving it at 0 (root). + out.Owner.Gid = 65534 + out.Owner.Uid = 65534 + // All the other fields stay 0. This is what cat sees (strace -v log): + // + // fstat(1, {st_dev=makedev(0, 0x4d), st_ino=3838227, st_mode=S_IFIFO|000, st_nlink=0, + // st_uid=65534, st_gid=65534, st_blksize=4096, st_blocks=0, st_size=0, st_atime=0, + // st_atime_nsec=0, st_mtime=0, st_mtime_nsec=0, st_ctime=0, st_ctime_nsec=0}) = 0 + goto out + } if errno != 0 { return } + defer syscall.Close(dirfd) - st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW) + st, err = syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW) if err != nil { return fs.ToErrno(err) } // Fix inode number - rn := n.rootNode() rn.inoMap.TranslateStat(st) out.Attr.FromStat(st) // Translate ciphertext size in `out.Attr.Size` to plaintext size n.translateSize(dirfd, cName, &out.Attr) +out: if rn.args.ForceOwner != nil { out.Owner = *rn.args.ForceOwner } diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go index 0f60c74..37d4293 100644 --- a/internal/fusefrontend/node_api_check.go +++ b/internal/fusefrontend/node_api_check.go @@ -7,7 +7,6 @@ import ( // Check that we have implemented the fs.Node* interfaces var _ = (fs.NodeGetattrer)((*Node)(nil)) var _ = (fs.NodeLookuper)((*Node)(nil)) -var _ = (fs.NodeReaddirer)((*Node)(nil)) var _ = (fs.NodeCreater)((*Node)(nil)) var _ = (fs.NodeMkdirer)((*Node)(nil)) var _ = (fs.NodeRmdirer)((*Node)(nil)) diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go index 97e4caa..11ff83d 100644 --- a/internal/fusefrontend/node_dir_ops.go +++ b/internal/fusefrontend/node_dir_ops.go @@ -12,7 +12,6 @@ import ( "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" - "github.com/rfjakob/gocryptfs/v2/internal/configfile" "github.com/rfjakob/gocryptfs/v2/internal/cryptocore" "github.com/rfjakob/gocryptfs/v2/internal/nametransform" "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat" @@ -159,91 +158,6 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En return ch, 0 } -// Readdir - FUSE call. -// -// This function is symlink-safe through use of openBackingDir() and -// ReadDirIVAt(). -func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { - parentDirFd, cDirName, errno := n.prepareAtSyscallMyself() - if errno != 0 { - return nil, errno - } - defer syscall.Close(parentDirFd) - - // Read ciphertext directory - fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) - if err != nil { - return nil, fs.ToErrno(err) - } - defer syscall.Close(fd) - cipherEntries, specialEntries, err := syscallcompat.GetdentsSpecial(fd) - if err != nil { - return nil, fs.ToErrno(err) - } - // Get DirIV (stays nil if PlaintextNames is used) - var cachedIV []byte - rn := n.rootNode() - if !rn.args.PlaintextNames { - // Read the DirIV from disk - cachedIV, err = rn.nameTransform.ReadDirIVAt(fd) - if err != nil { - tlog.Warn.Printf("OpenDir %q: could not read %s: %v", cDirName, nametransform.DirIVFilename, err) - return nil, syscall.EIO - } - } - // Decrypted directory entries - var plain []fuse.DirEntry - // Add "." and ".." - plain = append(plain, specialEntries...) - // Filter and decrypt filenames - for i := range cipherEntries { - cName := cipherEntries[i].Name - if n.IsRoot() && cName == configfile.ConfDefaultName { - // silently ignore "gocryptfs.conf" in the top level dir - continue - } - if rn.args.PlaintextNames { - plain = append(plain, cipherEntries[i]) - continue - } - if !rn.args.DeterministicNames && cName == nametransform.DirIVFilename { - // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled - continue - } - // Handle long file name - isLong := nametransform.LongNameNone - if rn.args.LongNames { - isLong = nametransform.NameType(cName) - } - if isLong == nametransform.LongNameContent { - cNameLong, err := nametransform.ReadLongNameAt(fd, cName) - if err != nil { - tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v", - cDirName, cName, err) - rn.reportMitigatedCorruption(cName) - continue - } - cName = cNameLong - } else if isLong == nametransform.LongNameFilename { - // ignore "gocryptfs.longname.*.name" - continue - } - name, err := rn.nameTransform.DecryptName(cName, cachedIV) - if err != nil { - tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v", - cDirName, cName, err) - rn.reportMitigatedCorruption(cName) - continue - } - // Override the ciphertext name with the plaintext name but reuse the rest - // of the structure - cipherEntries[i].Name = name - plain = append(plain, cipherEntries[i]) - } - - return fs.NewListDirStream(plain), 0 -} - // Rmdir - FUSE call. // // Symlink-safe through Unlinkat() + AT_REMOVEDIR. diff --git a/internal/fusefrontend/node_helpers.go b/internal/fusefrontend/node_helpers.go index f5dfeb6..e8fca80 100644 --- a/internal/fusefrontend/node_helpers.go +++ b/internal/fusefrontend/node_helpers.go @@ -2,7 +2,6 @@ package fusefrontend import ( "context" - "sync/atomic" "syscall" "github.com/hanwen/go-fuse/v2/fs" @@ -91,12 +90,12 @@ func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.Entry if rn.args.SharedStorage || rn.quirks&syscallcompat.QuirkDuplicateIno1 != 0 { // Make each directory entry a unique node by using a unique generation // value - see the comment at RootNode.gen for details. - gen = atomic.AddUint64(&rn.gen, 1) + gen = rn.gen.Add(1) } // Create child node id := fs.StableAttr{ - Mode: uint32(st.Mode), + Mode: uint32(st.Mode), // go-fuse masks this with syscall.S_IFMT Gen: gen, Ino: st.Ino, } diff --git a/internal/fusefrontend/node_prepare_syscall.go b/internal/fusefrontend/node_prepare_syscall.go index 2a4d6ab..9021350 100644 --- a/internal/fusefrontend/node_prepare_syscall.go +++ b/internal/fusefrontend/node_prepare_syscall.go @@ -1,7 +1,6 @@ package fusefrontend import ( - "sync/atomic" "syscall" "github.com/rfjakob/gocryptfs/v2/internal/tlog" @@ -24,7 +23,7 @@ func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno sy // All filesystem operations go through here, so this is a good place // to reset the idle marker. - atomic.StoreUint32(&rn.IsIdle, 0) + rn.IsIdle.Store(false) if n.IsRoot() && rn.isFiltered(child) { return -1, "", syscall.EPERM diff --git a/internal/fusefrontend/prepare_syscall_test.go b/internal/fusefrontend/prepare_syscall_test.go index acddaf3..e2c7d08 100644 --- a/internal/fusefrontend/prepare_syscall_test.go +++ b/internal/fusefrontend/prepare_syscall_test.go @@ -1,6 +1,7 @@ package fusefrontend import ( + "context" "strings" "syscall" "testing" @@ -22,13 +23,13 @@ func TestPrepareAtSyscall(t *testing.T) { rn := newTestFS(args) out := &fuse.EntryOut{} - child, errno := rn.Mkdir(nil, "dir1", 0700, out) + child, errno := rn.Mkdir(context.TODO(), "dir1", 0700, out) if errno != 0 { t.Fatal(errno) } rn.AddChild("dir1", child, false) dir1 := toNode(child.Operations()) - _, errno = dir1.Mkdir(nil, "dir2", 0700, out) + _, errno = dir1.Mkdir(context.TODO(), "dir2", 0700, out) if errno != 0 { t.Fatal(errno) } @@ -43,7 +44,7 @@ func TestPrepareAtSyscall(t *testing.T) { syscall.Close(dirfd) // Again, but populate the cache for "" by looking up a non-existing file - rn.Lookup(nil, "xyz1234", &fuse.EntryOut{}) + rn.Lookup(context.TODO(), "xyz1234", &fuse.EntryOut{}) dirfd, cName, errno = rn.prepareAtSyscallMyself() if errno != 0 { t.Fatal(errno) @@ -89,7 +90,7 @@ func TestPrepareAtSyscall(t *testing.T) { syscall.Close(dirfd) n255 := strings.Repeat("n", 255) - dir1.Mkdir(nil, n255, 0700, out) + dir1.Mkdir(context.TODO(), n255, 0700, out) dirfd, cName, errno = dir1.prepareAtSyscall(n255) if errno != 0 { t.Fatal(errno) @@ -116,13 +117,13 @@ func TestPrepareAtSyscallPlaintextnames(t *testing.T) { rn := newTestFS(args) out := &fuse.EntryOut{} - child, errno := rn.Mkdir(nil, "dir1", 0700, out) + child, errno := rn.Mkdir(context.TODO(), "dir1", 0700, out) if errno != 0 { t.Fatal(errno) } rn.AddChild("dir1", child, false) dir1 := toNode(child.Operations()) - _, errno = dir1.Mkdir(nil, "dir2", 0700, out) + _, errno = dir1.Mkdir(context.TODO(), "dir2", 0700, out) if errno != 0 { t.Fatal(errno) } diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go index ac814ad..8464c5f 100644 --- a/internal/fusefrontend/root_node.go +++ b/internal/fusefrontend/root_node.go @@ -4,6 +4,7 @@ import ( "os" "strings" "sync" + "sync/atomic" "syscall" "time" @@ -44,7 +45,7 @@ type RootNode struct { // (uint32 so that it can be reset with CompareAndSwapUint32). // When -idle was used when mounting, idleMonitor() sets it to 1 // periodically. - IsIdle uint32 + IsIdle atomic.Bool // dirCache caches directory fds dirCache dirCache // inoMap translates inode numbers from different devices to unique inode @@ -55,7 +56,7 @@ type RootNode struct { // This makes each directory entry unique (even hard links), // makes go-fuse hand out separate FUSE Node IDs for each, and prevents // bizarre problems when inode numbers are reused behind our back. - gen uint64 + gen atomic.Uint64 // quirks is a bitmap that enables workaround for quirks in the filesystem // backing the cipherdir quirks uint64 diff --git a/internal/fusefrontend/xattr_unit_test.go b/internal/fusefrontend/xattr_unit_test.go index 86c87a7..7d8e32e 100644 --- a/internal/fusefrontend/xattr_unit_test.go +++ b/internal/fusefrontend/xattr_unit_test.go @@ -21,10 +21,10 @@ func newTestFS(args Args) *RootNode { cEnc := contentenc.New(cCore, contentenc.DefaultBS) n := nametransform.New(cCore.EMECipher, true, 0, true, nil, false) rn := NewRootNode(args, cEnc, n) - oneSec := time.Second + oneSecond := time.Second options := &fs.Options{ - EntryTimeout: &oneSec, - AttrTimeout: &oneSec, + EntryTimeout: &oneSecond, + AttrTimeout: &oneSecond, } fs.NewNodeFS(rn, options) return rn diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go index 0faadfa..1cb4b80 100644 --- a/internal/fusefrontend_reverse/excluder.go +++ b/internal/fusefrontend_reverse/excluder.go @@ -1,7 +1,6 @@ package fusefrontend_reverse import ( - "io/ioutil" "log" "os" "strings" @@ -50,7 +49,7 @@ func getExclusionPatterns(args fusefrontend.Args) []string { // getLines reads a file and splits it into lines func getLines(file string) ([]string, error) { - buffer, err := ioutil.ReadFile(file) + buffer, err := os.ReadFile(file) if err != nil { return nil, err } diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go index bb041ce..b44ddce 100644 --- a/internal/fusefrontend_reverse/excluder_test.go +++ b/internal/fusefrontend_reverse/excluder_test.go @@ -1,7 +1,6 @@ package fusefrontend_reverse import ( - "io/ioutil" "os" "reflect" "testing" @@ -23,7 +22,7 @@ func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) { } func TestShouldReadExcludePatternsFromFiles(t *testing.T) { - tmpfile1, err := ioutil.TempFile("", "excludetest") + tmpfile1, err := os.CreateTemp("", "excludetest") if err != nil { t.Fatal(err) } @@ -31,7 +30,7 @@ func TestShouldReadExcludePatternsFromFiles(t *testing.T) { defer os.Remove(exclude1) defer tmpfile1.Close() - tmpfile2, err := ioutil.TempFile("", "excludetest") + tmpfile2, err := os.CreateTemp("", "excludetest") if err != nil { t.Fatal(err) } diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go index 30361bc..3165db6 100644 --- a/internal/fusefrontend_reverse/node_helpers.go +++ b/internal/fusefrontend_reverse/node_helpers.go @@ -24,7 +24,6 @@ const ( // * base64(192 bytes) = 256 bytes (over 255!) // But the PKCS#7 padding is at least one byte. This means we can only use // 175 bytes for the file name. - shortNameMax = 175 ) // translateSize translates the ciphertext size in `out` into plaintext size. @@ -175,7 +174,7 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod errno = fs.ToErrno(err) return } - content := pathiv.Derive(d.cPath, pathiv.PurposeDirIV) + content := rn.deriveDirIV(d.cPath) var vf *VirtualMemNode vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV) if errno != 0 { diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go index cb04151..9c2de28 100644 --- a/internal/fusefrontend_reverse/root_node.go +++ b/internal/fusefrontend_reverse/root_node.go @@ -21,7 +21,7 @@ import ( "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat" "github.com/rfjakob/gocryptfs/v2/internal/tlog" - "github.com/sabhiram/go-gitignore" + ignore "github.com/sabhiram/go-gitignore" ) // RootNode is the root directory in a `gocryptfs -reverse` mount @@ -50,7 +50,7 @@ type RootNode struct { // makes go-fuse hand out separate FUSE Node IDs for each, and prevents // bizarre problems when inode numbers are reused behind our back, // like this one: https://github.com/rfjakob/gocryptfs/issues/802 - gen uint64 + gen atomic.Uint64 // rootIno is the inode number that we report for the root node on mount rootIno uint64 } @@ -175,7 +175,7 @@ func (rn *RootNode) uniqueStableAttr(mode uint32, ino uint64) fs.StableAttr { Ino: ino, // Make each directory entry a unique node by using a unique generation // value. Also see the comment at RootNode.gen for details. - Gen: atomic.AddUint64(&rn.gen, 1), + Gen: rn.gen.Add(1), } } diff --git a/internal/fusefrontend_reverse/virtualnode.go b/internal/fusefrontend_reverse/virtualnode.go index 732564a..95e71ab 100644 --- a/internal/fusefrontend_reverse/virtualnode.go +++ b/internal/fusefrontend_reverse/virtualnode.go @@ -100,6 +100,15 @@ func (n *Node) newVirtualMemNode(content []byte, parentStat *syscall.Stat_t, ino st.Nlink = 1 var a fuse.Attr a.FromStat(st) + // With inode number reuse and hard links, we could have returned + // wrong data for gocryptfs.diriv and gocryptfs.xyz.longname files, respectively + // (https://github.com/rfjakob/gocryptfs/issues/802). + // + // Now that this is fixed, ensure that rsync and similar tools pick up the new + // correct files by advancing mtime and ctime by 10 seconds, which should be more + // than any filesytems' timestamp granularity (FAT32 has 2 seconds). + a.Mtime += 10 + a.Ctime += 10 if rn.args.ForceOwner != nil { a.Owner = *rn.args.ForceOwner } diff --git a/internal/inomap/inomap.go b/internal/inomap/inomap.go index b4dbf27..5749202 100644 --- a/internal/inomap/inomap.go +++ b/internal/inomap/inomap.go @@ -45,7 +45,7 @@ type InoMap struct { // spill is used once the namespaces map is full spillMap map[QIno]uint64 // spillNext is the next free inode number in the spill map - spillNext uint64 + spillNext atomic.Uint64 } // New returns a new InoMap. @@ -57,8 +57,9 @@ func New(rootDev uint64) *InoMap { namespaceMap: make(map[namespaceData]uint16), namespaceNext: 0, spillMap: make(map[QIno]uint64), - spillNext: spillSpaceStart, } + m.spillNext.Store(spillSpaceStart) + if rootDev > 0 { // Reserve namespace 0 for rootDev m.namespaceMap[namespaceData{rootDev, 0}] = 0 @@ -74,10 +75,10 @@ var spillWarn sync.Once // Reverse mode NextSpillIno() for gocryptfs.longname.*.name files where a stable // mapping is not needed. func (m *InoMap) NextSpillIno() (out uint64) { - if m.spillNext == math.MaxUint64 { - log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext) + if m.spillNext.Load() == math.MaxUint64 { + log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext.Load()) } - return atomic.AddUint64(&m.spillNext, 1) - 1 + return m.spillNext.Add(1) - 1 } func (m *InoMap) spill(in QIno) (out uint64) { diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go index 7929c40..5dd4940 100644 --- a/internal/nametransform/diriv.go +++ b/internal/nametransform/diriv.go @@ -67,7 +67,7 @@ func fdReadDirIV(fd *os.File) (iv []byte, err error) { func WriteDirIVAt(dirfd int) error { iv := cryptocore.RandBytes(DirIVLen) // 0400 permissions: gocryptfs.diriv should never be modified after creation. - // Don't use "ioutil.WriteFile", it causes trouble on NFS: + // Don't use "os.WriteFile", it causes trouble on NFS: // https://github.com/rfjakob/gocryptfs/commit/7d38f80a78644c8ec4900cc990bfb894387112ed fd, err := syscallcompat.Openat(dirfd, DirIVFilename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, dirivPerms) if err != nil { diff --git a/internal/nametransform/pad16.go b/internal/nametransform/pad16.go index 833be0e..2c2466a 100644 --- a/internal/nametransform/pad16.go +++ b/internal/nametransform/pad16.go @@ -32,10 +32,10 @@ func pad16(orig []byte) (padded []byte) { func unPad16(padded []byte) ([]byte, error) { oldLen := len(padded) if oldLen == 0 { - return nil, errors.New("Empty input") + return nil, errors.New("empty input") } if oldLen%aes.BlockSize != 0 { - return nil, errors.New("Unaligned size") + return nil, errors.New("unaligned size") } // The last byte is always a padding byte padByte := padded[oldLen-1] @@ -43,20 +43,20 @@ func unPad16(padded []byte) ([]byte, error) { padLen := int(padByte) // Padding must be at least 1 byte if padLen == 0 { - return nil, errors.New("Padding cannot be zero-length") + return nil, errors.New("padding cannot be zero-length") } // Padding more than 16 bytes make no sense if padLen > aes.BlockSize { - return nil, fmt.Errorf("Padding too long, padLen=%d > 16", padLen) + return nil, fmt.Errorf("padding too long, padLen=%d > 16", padLen) } // Padding cannot be as long as (or longer than) the whole string, if padLen >= oldLen { - return nil, fmt.Errorf("Padding too long, oldLen=%d >= padLen=%d", oldLen, padLen) + return nil, fmt.Errorf("padding too long, oldLen=%d >= padLen=%d", oldLen, padLen) } // All padding bytes must be identical for i := oldLen - padLen; i < oldLen; i++ { if padded[i] != padByte { - return nil, fmt.Errorf("Padding byte at i=%d is invalid", i) + return nil, fmt.Errorf("padding byte at i=%d is invalid", i) } } newLen := oldLen - padLen diff --git a/internal/openfiletable/open_file_table.go b/internal/openfiletable/open_file_table.go index ce8df76..420d070 100644 --- a/internal/openfiletable/open_file_table.go +++ b/internal/openfiletable/open_file_table.go @@ -29,7 +29,7 @@ type table struct { // The variable is accessed without holding any locks so atomic operations // must be used. It must be the first element of the struct to guarantee // 64-bit alignment. - writeOpCount uint64 + writeOpCount atomic.Uint64 // Protects map access sync.Mutex // Table entries @@ -85,13 +85,13 @@ type countingMutex struct { func (c *countingMutex) Lock() { c.RWMutex.Lock() - atomic.AddUint64(&t.writeOpCount, 1) + t.writeOpCount.Add(1) } // WriteOpCount returns the write lock counter value. This value is incremented // each time writeLock.Lock() on a file table entry is called. func WriteOpCount() uint64 { - return atomic.LoadUint64(&t.writeOpCount) + return t.writeOpCount.Load() } // CountOpenFiles returns how many entries are currently in the table diff --git a/internal/readpassword/read.go b/internal/readpassword/read.go index 582a104..9193ae8 100644 --- a/internal/readpassword/read.go +++ b/internal/readpassword/read.go @@ -58,7 +58,7 @@ func Twice(extpass []string, passfile []string) ([]byte, error) { return nil, err } if !bytes.Equal(p1, p2) { - return nil, fmt.Errorf("Passwords do not match") + return nil, fmt.Errorf("passwords do not match") } // Wipe the password duplicate from memory for i := range p2 { @@ -71,15 +71,15 @@ func Twice(extpass []string, passfile []string) ([]byte, error) { // Exits on read error or empty result. func readPasswordTerminal(prompt string) ([]byte, error) { fd := int(os.Stdin.Fd()) - fmt.Fprintf(os.Stderr, prompt) + fmt.Fprint(os.Stderr, prompt) // term.ReadPassword removes the trailing newline p, err := term.ReadPassword(fd) if err != nil { - return nil, fmt.Errorf("Could not read password from terminal: %v\n", err) + return nil, fmt.Errorf("could not read password from terminal: %v", err) } fmt.Fprintf(os.Stderr, "\n") if len(p) == 0 { - return nil, fmt.Errorf("Password is empty") + return nil, fmt.Errorf("password is empty") } return p, nil } @@ -100,7 +100,7 @@ func readPasswordStdin(prompt string) ([]byte, error) { return nil, err } if len(p) == 0 { - return nil, fmt.Errorf("Got empty %s from stdin", prompt) + return nil, fmt.Errorf("got empty %s from stdin", prompt) } return p, nil } diff --git a/internal/siv_aead/correctness_test.go b/internal/siv_aead/correctness_test.go index 0653e26..7be97bc 100644 --- a/internal/siv_aead/correctness_test.go +++ b/internal/siv_aead/correctness_test.go @@ -50,7 +50,7 @@ func TestK32(t *testing.T) { expectedResult, _ := hex.DecodeString( "02020202020202020202020202020202ad7a4010649a84d8c1dd5f752e935eed57d45b8b10008f3834") if !bytes.Equal(aResult, expectedResult) { - t.Errorf(hex.EncodeToString(aResult)) + t.Error(hex.EncodeToString(aResult)) } // Verify overhead overhead := len(aResult) - len(plaintext) - len(nonce) @@ -108,7 +108,7 @@ func TestK64(t *testing.T) { expectedResult, _ := hex.DecodeString( "02020202020202020202020202020202317b316f67c3ad336c01c9a01b4c5e552ba89e966bc4c1ade1") if !bytes.Equal(aResult, expectedResult) { - t.Errorf(hex.EncodeToString(aResult)) + t.Error(hex.EncodeToString(aResult)) } // Verify overhead overhead := len(aResult) - len(plaintext) - len(nonce) diff --git a/internal/speed/cpuinfo.go b/internal/speed/cpuinfo.go index df3177d..636a4f0 100644 --- a/internal/speed/cpuinfo.go +++ b/internal/speed/cpuinfo.go @@ -1,7 +1,7 @@ package speed import ( - "io/ioutil" + "io" "os" "runtime" "strings" @@ -33,7 +33,7 @@ func cpuModelName() string { if err != nil { return "" } - content, err := ioutil.ReadAll(f) + content, err := io.ReadAll(f) if err != nil { return "" } diff --git a/internal/syscallcompat/asuser.go b/internal/syscallcompat/asuser.go new file mode 100644 index 0000000..0c083ec --- /dev/null +++ b/internal/syscallcompat/asuser.go @@ -0,0 +1,59 @@ +package syscallcompat + +import ( + "golang.org/x/sys/unix" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +// OpenatUser runs the Openat syscall in the context of a different user. +// It switches the current thread to the new user, performs the syscall, +// and switches back. +// +// If `context` is nil, this function behaves like ordinary Openat (no +// user switching). +func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) { + f := func() (int, error) { + return Openat(dirfd, path, flags, mode) + } + return asUser(f, context) +} + +// MknodatUser runs the Mknodat syscall in the context of a different user. +// If `context` is nil, this function behaves like ordinary Mknodat. +// +// See OpenatUser() for how this works. +func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) { + f := func() (int, error) { + err := Mknodat(dirfd, path, mode, dev) + return -1, err + } + _, err = asUser(f, context) + return err +} + +// SymlinkatUser runs the Symlinkat syscall in the context of a different user. +// If `context` is nil, this function behaves like ordinary Symlinkat. +// +// See OpenatUser() for how this works. +func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) { + f := func() (int, error) { + err := unix.Symlinkat(oldpath, newdirfd, newpath) + return -1, err + } + _, err = asUser(f, context) + return err +} + +// MkdiratUser runs the Mkdirat syscall in the context of a different user. +// If `context` is nil, this function behaves like ordinary Mkdirat. +// +// See OpenatUser() for how this works. +func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) { + f := func() (int, error) { + err := unix.Mkdirat(dirfd, path, mode) + return -1, err + } + _, err = asUser(f, context) + return err +} diff --git a/internal/syscallcompat/asuser_darwin.go b/internal/syscallcompat/asuser_darwin.go new file mode 100644 index 0000000..5da2782 --- /dev/null +++ b/internal/syscallcompat/asuser_darwin.go @@ -0,0 +1,46 @@ +package syscallcompat + +import ( + "runtime" + "syscall" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +// asUser runs `f()` under the effective uid, gid, groups specified +// in `context`. +// +// If `context` is nil, `f()` is executed directly without switching user id. +func asUser(f func() (int, error), context *fuse.Context) (int, error) { + if context == nil { + return f() + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := pthread_setugid_np(context.Owner.Uid, context.Owner.Gid) + if err != nil { + return -1, err + } + + const ( + // KAUTH_UID_NONE and KAUTH_GID_NONE are special values to + // revert permissions to the process credentials. + KAUTH_UID_NONE = ^uint32(0) - 100 + KAUTH_GID_NONE = ^uint32(0) - 100 + ) + + defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE) + + return f() +} + +// Unfortunately pthread_setugid_np does not have a syscall wrapper yet. +func pthread_setugid_np(uid uint32, gid uint32) (err error) { + _, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0) + if e1 != 0 { + err = e1 + } + return +} diff --git a/internal/syscallcompat/asuser_linux.go b/internal/syscallcompat/asuser_linux.go new file mode 100644 index 0000000..39e3ff2 --- /dev/null +++ b/internal/syscallcompat/asuser_linux.go @@ -0,0 +1,80 @@ +package syscallcompat + +import ( + "fmt" + "os" + "runtime" + "strconv" + "strings" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +// asUser runs `f()` under the effective uid, gid, groups specified +// in `context`. +// +// If `context` is nil, `f()` is executed directly without switching user id. +func asUser(f func() (int, error), context *fuse.Context) (int, error) { + if context == nil { + return f() + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + // Since go1.16beta1 (commit d1b1145cace8b968307f9311ff611e4bb810710c , + // https://go-review.googlesource.com/c/go/+/210639 ) + // syscall.{Setgroups,Setregid,Setreuid} affects all threads, which + // is exactly what we not want. + // + // And unix.{Setgroups,Setregid,Setreuid} also changed to this behavoir in + // v0.1.0 (commit d0df966e6959f00dc1c74363e537872647352d51 , + // https://go-review.googlesource.com/c/sys/+/428174 ), so we use + // our own syscall wrappers. + + err := Setgroups(getSupplementaryGroups(context.Pid)) + if err != nil { + return -1, err + } + defer SetgroupsPanic(nil) + + err = Setregid(-1, int(context.Owner.Gid)) + if err != nil { + return -1, err + } + defer SetregidPanic(-1, 0) + + err = Setreuid(-1, int(context.Owner.Uid)) + if err != nil { + return -1, err + } + defer SetreuidPanic(-1, 0) + + return f() +} + +func getSupplementaryGroups(pid uint32) (gids []int) { + procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid) + blob, err := os.ReadFile(procPath) + if err != nil { + return nil + } + + lines := strings.Split(string(blob), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "Groups:") { + f := strings.Fields(line[7:]) + gids = make([]int, len(f)) + for i := range gids { + val, err := strconv.ParseInt(f[i], 10, 32) + if err != nil { + return nil + } + gids[i] = int(val) + } + return gids + } + } + + return nil +} diff --git a/internal/syscallcompat/getdents_test.go b/internal/syscallcompat/getdents_test.go index eb670d6..c9e6a99 100644 --- a/internal/syscallcompat/getdents_test.go +++ b/internal/syscallcompat/getdents_test.go @@ -4,7 +4,6 @@ package syscallcompat import ( - "io/ioutil" "os" "runtime" "strings" @@ -49,13 +48,13 @@ func testGetdents(t *testing.T) { getdentsUnderTest = emulateGetdents } // Fill a directory with filenames of length 1 ... 255 - testDir, err := ioutil.TempDir(tmpDir, "TestGetdents") + testDir, err := os.MkdirTemp(tmpDir, "TestGetdents") if err != nil { t.Fatal(err) } for i := 1; i <= unix.NAME_MAX; i++ { n := strings.Repeat("x", i) - err = ioutil.WriteFile(testDir+"/"+n, nil, 0600) + err = os.WriteFile(testDir+"/"+n, nil, 0600) if err != nil { t.Fatal(err) } diff --git a/internal/syscallcompat/main_test.go b/internal/syscallcompat/main_test.go index ddf6bc4..7183f5a 100644 --- a/internal/syscallcompat/main_test.go +++ b/internal/syscallcompat/main_test.go @@ -2,7 +2,6 @@ package syscallcompat import ( "fmt" - "io/ioutil" "os" "testing" ) @@ -23,7 +22,7 @@ func TestMain(m *testing.M) { fmt.Println(err) os.Exit(1) } - tmpDir, err = ioutil.TempDir(parent, "syscallcompat") + tmpDir, err = os.MkdirTemp(parent, "syscallcompat") if err != nil { fmt.Println(err) os.Exit(1) diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go index 06f09f0..cf2f3f0 100644 --- a/internal/syscallcompat/sys_darwin.go +++ b/internal/syscallcompat/sys_darwin.go @@ -3,7 +3,6 @@ package syscallcompat import ( "log" "path/filepath" - "runtime" "syscall" "time" "unsafe" @@ -26,22 +25,8 @@ const ( RENAME_NOREPLACE = 1 RENAME_EXCHANGE = 2 RENAME_WHITEOUT = 4 - - // KAUTH_UID_NONE and KAUTH_GID_NONE are special values to - // revert permissions to the process credentials. - KAUTH_UID_NONE = ^uint32(0) - 100 - KAUTH_GID_NONE = ^uint32(0) - 100 ) -// Unfortunately pthread_setugid_np does not have a syscall wrapper yet. -func pthread_setugid_np(uid uint32, gid uint32) (err error) { - _, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0) - if e1 != 0 { - err = e1 - } - return -} - // Unfortunately fsetattrlist does not have a syscall wrapper yet. func fsetattrlist(fd int, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) { _, _, e1 := syscall.Syscall6(syscall.SYS_FSETATTRLIST, uintptr(fd), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0) @@ -84,74 +69,14 @@ func Dup3(oldfd int, newfd int, flags int) (err error) { //// Emulated Syscalls (see emulate.go) //////////////// //////////////////////////////////////////////////////// -func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) { - if context != nil { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid) - if err != nil { - return -1, err - } - defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE) - } - - return Openat(dirfd, path, flags, mode) -} - func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) { return emulateMknodat(dirfd, path, mode, dev) } -func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) { - if context != nil { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid) - if err != nil { - return err - } - defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE) - } - - return Mknodat(dirfd, path, mode, dev) -} - func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) { return unix.Fchmodat(dirfd, path, mode, unix.AT_SYMLINK_NOFOLLOW) } -func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) { - if context != nil { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid) - if err != nil { - return err - } - defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE) - } - - return unix.Symlinkat(oldpath, newdirfd, newpath) -} - -func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) { - if context != nil { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid) - if err != nil { - return err - } - defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE) - } - - return unix.Mkdirat(dirfd, path, mode) -} - type attrList struct { bitmapCount uint16 _ uint16 diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go index a64b27e..19d2c56 100644 --- a/internal/syscallcompat/sys_linux.go +++ b/internal/syscallcompat/sys_linux.go @@ -3,10 +3,6 @@ package syscallcompat import ( "fmt" - "io/ioutil" - "runtime" - "strconv" - "strings" "sync" "syscall" "time" @@ -67,104 +63,11 @@ func Fallocate(fd int, mode uint32, off int64, len int64) (err error) { return syscall.Fallocate(fd, mode, off, len) } -func getSupplementaryGroups(pid uint32) (gids []int) { - procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid) - blob, err := ioutil.ReadFile(procPath) - if err != nil { - return nil - } - - lines := strings.Split(string(blob), "\n") - for _, line := range lines { - if strings.HasPrefix(line, "Groups:") { - f := strings.Fields(line[7:]) - gids = make([]int, len(f)) - for i := range gids { - val, err := strconv.ParseInt(f[i], 10, 32) - if err != nil { - return nil - } - gids[i] = int(val) - } - return gids - } - } - - return nil -} - -// asUser runs `f()` under the effective uid, gid, groups specified -// in `context`. -// -// If `context` is nil, `f()` is executed directly without switching user id. -func asUser(f func() (int, error), context *fuse.Context) (int, error) { - if context == nil { - return f() - } - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - // Since go1.16beta1 (commit d1b1145cace8b968307f9311ff611e4bb810710c , - // https://go-review.googlesource.com/c/go/+/210639 ) - // syscall.{Setgroups,Setregid,Setreuid} affects all threads, which - // is exactly what we not want. - // - // We now use unix.{Setgroups,Setregid,Setreuid} instead. - - err := unix.Setgroups(getSupplementaryGroups(context.Pid)) - if err != nil { - return -1, err - } - defer unix.Setgroups(nil) - - err = unix.Setregid(-1, int(context.Owner.Gid)) - if err != nil { - return -1, err - } - defer unix.Setregid(-1, 0) - - err = unix.Setreuid(-1, int(context.Owner.Uid)) - if err != nil { - return -1, err - } - defer unix.Setreuid(-1, 0) - - return f() -} - -// OpenatUser runs the Openat syscall in the context of a different user. -// -// It switches the current thread to the new user, performs the syscall, -// and switches back. -// -// If `context` is nil, this function behaves like ordinary Openat (no -// user switching). -func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) { - f := func() (int, error) { - return Openat(dirfd, path, flags, mode) - } - return asUser(f, context) -} - // Mknodat wraps the Mknodat syscall. func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) { return syscall.Mknodat(dirfd, path, mode, dev) } -// MknodatUser runs the Mknodat syscall in the context of a different user. -// If `context` is nil, this function behaves like ordinary Mknodat. -// -// See OpenatUser() for how this works. -func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) { - f := func() (int, error) { - err := Mknodat(dirfd, path, mode, dev) - return -1, err - } - _, err = asUser(f, context) - return err -} - // Dup3 wraps the Dup3 syscall. We want to use Dup3 rather than Dup2 because Dup2 // is not implemented on arm64. func Dup3(oldfd int, newfd int, flags int) (err error) { @@ -205,32 +108,6 @@ func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) { return syscall.Chmod(procPath, mode) } -// SymlinkatUser runs the Symlinkat syscall in the context of a different user. -// If `context` is nil, this function behaves like ordinary Symlinkat. -// -// See OpenatUser() for how this works. -func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) { - f := func() (int, error) { - err := unix.Symlinkat(oldpath, newdirfd, newpath) - return -1, err - } - _, err = asUser(f, context) - return err -} - -// MkdiratUser runs the Mkdirat syscall in the context of a different user. -// If `context` is nil, this function behaves like ordinary Mkdirat. -// -// See OpenatUser() for how this works. -func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) { - f := func() (int, error) { - err := unix.Mkdirat(dirfd, path, mode) - return -1, err - } - _, err = asUser(f, context) - return err -} - // LsetxattrUser runs the Lsetxattr syscall in the context of a different user. // This is useful when setting ACLs, as the result depends on the user running // the operation (see fuse-xfstests generic/375). @@ -247,8 +124,16 @@ func LsetxattrUser(path string, attr string, data []byte, flags int, context *fu func timesToTimespec(a *time.Time, m *time.Time) []unix.Timespec { ts := make([]unix.Timespec, 2) - ts[0] = unix.Timespec(fuse.UtimeToTimespec(a)) - ts[1] = unix.Timespec(fuse.UtimeToTimespec(m)) + if a == nil { + ts[0] = unix.Timespec{Nsec: unix.UTIME_OMIT} + } else { + ts[0], _ = unix.TimeToTimespec(*a) + } + if m == nil { + ts[1] = unix.Timespec{Nsec: unix.UTIME_OMIT} + } else { + ts[1], _ = unix.TimeToTimespec(*m) + } return ts } diff --git a/internal/syscallcompat/thread_credentials_linux.go b/internal/syscallcompat/thread_credentials_linux.go new file mode 100644 index 0000000..b5ec6cd --- /dev/null +++ b/internal/syscallcompat/thread_credentials_linux.go @@ -0,0 +1,61 @@ +//go:build linux + +// golang.org/x/sys/unix commit +// https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51 +// changed unix.Setreuid/unix.Setregid functions to affect the whole thread, which is +// what gocryptfs does NOT want (https://github.com/rfjakob/gocryptfs/issues/893). +// The functions Setreuid/Setegid are copy-pasted from one commit before +// (9e1f76180b77a12eb07c82eb8e1ea8a7f8d202e7). +// +// Looking at the diff at https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51 +// we see that only two architectures, 386 and arm, use SYS_SETREUID32/SYS_SETREGID32 +// (see "man 2 setreuid" for why). +// All the others architectures use SYS_SETREUID/SYS_SETREGID. +// +// As of golang.org/x/sys/unix v0.30.0, Setgroups/setgroups is still per-thread, but +// it is likely that this will change, too. Setgroups/setgroups are copy-pasted from +// v0.30.0. The SYS_SETGROUPS32/SYS_SETGROUPS split is the same as for Setreuid. +// +// Note: _Gid_t is always uint32 on linux, so we can directly use uint32 for setgroups. +package syscallcompat + +import ( + "log" +) + +// Setgroups is like setgroups(2) but affects only the current thread +func Setgroups(gids []int) (err error) { + if len(gids) == 0 { + return setgroups(0, nil) + } + + a := make([]uint32, len(gids)) + for i, v := range gids { + a[i] = uint32(v) + } + return setgroups(len(a), &a[0]) +} + +// SetgroupsPanic calls Setgroups and panics on error +func SetgroupsPanic(gids []int) { + err := Setgroups(gids) + if err != nil { + log.Panic(err) + } +} + +// SetregidPanic calls Setregid and panics on error +func SetregidPanic(rgid int, egid int) { + err := Setregid(rgid, egid) + if err != nil { + log.Panic(err) + } +} + +// SetreuidPanic calls Setreuid and panics on error +func SetreuidPanic(ruid int, euid int) { + err := Setreuid(ruid, euid) + if err != nil { + log.Panic(err) + } +} diff --git a/internal/syscallcompat/thread_credentials_linux_32.go b/internal/syscallcompat/thread_credentials_linux_32.go new file mode 100644 index 0000000..69fffca --- /dev/null +++ b/internal/syscallcompat/thread_credentials_linux_32.go @@ -0,0 +1,40 @@ +//go:build (linux && 386) || (linux && arm) + +// Linux on i386 and 32-bit ARM has SYS_SETREUID and friends returning 16-bit values. +// We need to use SYS_SETREUID32 instead. + +package syscallcompat + +import ( + "unsafe" + + "golang.org/x/sys/unix" +) + +// See thread_credentials_linux.go for docs + +// Setreuid is like setreuid(2) but affects only the current thread +func Setreuid(ruid int, euid int) (err error) { + _, _, e1 := unix.RawSyscall(unix.SYS_SETREUID32, uintptr(ruid), uintptr(euid), 0) + if e1 != 0 { + err = e1 + } + return +} + +// Setreuid is like setregid(2) but affects only the current thread +func Setregid(rgid int, egid int) (err error) { + _, _, e1 := unix.RawSyscall(unix.SYS_SETREGID32, uintptr(rgid), uintptr(egid), 0) + if e1 != 0 { + err = e1 + } + return +} + +func setgroups(n int, list *uint32) (err error) { + _, _, e1 := unix.RawSyscall(unix.SYS_SETGROUPS32, uintptr(n), uintptr(unsafe.Pointer(list)), 0) + if e1 != 0 { + err = e1 + } + return +} diff --git a/internal/syscallcompat/thread_credentials_linux_other.go b/internal/syscallcompat/thread_credentials_linux_other.go new file mode 100644 index 0000000..ab11c71 --- /dev/null +++ b/internal/syscallcompat/thread_credentials_linux_other.go @@ -0,0 +1,37 @@ +//go:build !((linux && 386) || (linux && arm)) + +package syscallcompat + +import ( + "unsafe" + + "golang.org/x/sys/unix" +) + +// See thread_credentials_linux.go for docs + +// Setreuid is like setreuid(2) but affects only the current thread +func Setreuid(ruid int, euid int) (err error) { + _, _, e1 := unix.RawSyscall(unix.SYS_SETREUID, uintptr(ruid), uintptr(euid), 0) + if e1 != 0 { + err = e1 + } + return +} + +// Setreuid is like setregid(2) but affects only the current thread +func Setregid(rgid int, egid int) (err error) { + _, _, e1 := unix.RawSyscall(unix.SYS_SETREGID, uintptr(rgid), uintptr(egid), 0) + if e1 != 0 { + err = e1 + } + return +} + +func setgroups(n int, list *uint32) (err error) { + _, _, e1 := unix.RawSyscall(unix.SYS_SETGROUPS, uintptr(n), uintptr(unsafe.Pointer(list)), 0) + if e1 != 0 { + err = e1 + } + return +} |