diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend_reverse/file.go | 70 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/file_api_check.go | 23 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/file_helpers.go | 62 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/node.go | 86 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/node_api_check.go | 6 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/node_helpers.go | 33 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/virtualconf.go | 55 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/virtualnode.go (renamed from internal/fusefrontend_reverse/virtualfile.go) | 16 | 
8 files changed, 327 insertions, 24 deletions
| diff --git a/internal/fusefrontend_reverse/file.go b/internal/fusefrontend_reverse/file.go new file mode 100644 index 0000000..55f5f80 --- /dev/null +++ b/internal/fusefrontend_reverse/file.go @@ -0,0 +1,70 @@ +package fusefrontend_reverse + +import ( +	"bytes" +	"context" +	"os" +	"syscall" + +	"github.com/hanwen/go-fuse/v2/fs" +	"github.com/hanwen/go-fuse/v2/fuse" + +	"github.com/rfjakob/gocryptfs/internal/contentenc" +) + +type File struct { +	// Backing FD +	fd *os.File +	// File header (contains the IV) +	header contentenc.FileHeader +	// IV for block 0 +	block0IV []byte +	// Content encryption helper +	contentEnc *contentenc.ContentEnc +} + +// Read - FUSE call +func (f *File) Read(ctx context.Context, buf []byte, ioff int64) (resultData fuse.ReadResult, errno syscall.Errno) { +	length := uint64(len(buf)) +	off := uint64(ioff) +	out := bytes.NewBuffer(buf[:0]) +	var header []byte + +	// Synthesize file header +	if off < contentenc.HeaderLen { +		header = f.header.Pack() +		// Truncate to requested part +		end := int(off) + len(buf) +		if end > len(header) { +			end = len(header) +		} +		header = header[off:end] +		// Write into output buffer and adjust offsets +		out.Write(header) +		hLen := uint64(len(header)) +		off += hLen +		length -= hLen +	} + +	// Read actual file data +	if length > 0 { +		fileData, err := f.readBackingFile(off, length) +		if err != nil { +			return nil, fs.ToErrno(err) +		} +		if len(fileData) == 0 { +			// If we could not read any actual data, we also don't want to +			// return the file header. An empty file stays empty in encrypted +			// form. +			return nil, 0 +		} +		out.Write(fileData) +	} + +	return fuse.ReadResultData(out.Bytes()), 0 +} + +// Release - FUSE call, close file +func (f *File) Release(context.Context) syscall.Errno { +	return fs.ToErrno(f.fd.Close()) +} diff --git a/internal/fusefrontend_reverse/file_api_check.go b/internal/fusefrontend_reverse/file_api_check.go new file mode 100644 index 0000000..6dd1771 --- /dev/null +++ b/internal/fusefrontend_reverse/file_api_check.go @@ -0,0 +1,23 @@ +package fusefrontend_reverse + +import ( +	"github.com/hanwen/go-fuse/v2/fs" +) + +// Check that we have implemented the fs.File* interfaces +var _ = (fs.FileReader)((*File)(nil)) +var _ = (fs.FileReleaser)((*File)(nil)) + +/* TODO +var _ = (fs.FileGetattrer)((*File2)(nil)) +var _ = (fs.FileSetattrer)((*File2)(nil)) +var _ = (fs.FileWriter)((*File2)(nil)) +var _ = (fs.FileFsyncer)((*File2)(nil)) +var _ = (fs.FileFlusher)((*File2)(nil)) +var _ = (fs.FileAllocater)((*File2)(nil)) +var _ = (fs.FileLseeker)((*File2)(nil)) +var _ = (fs.FileHandle)((*File2)(nil)) +var _ = (fs.FileGetlker)((*File2)(nil)) +var _ = (fs.FileSetlker)((*File2)(nil)) +var _ = (fs.FileSetlkwer)((*File2)(nil)) +*/ diff --git a/internal/fusefrontend_reverse/file_helpers.go b/internal/fusefrontend_reverse/file_helpers.go new file mode 100644 index 0000000..f024e69 --- /dev/null +++ b/internal/fusefrontend_reverse/file_helpers.go @@ -0,0 +1,62 @@ +package fusefrontend_reverse + +import ( +	"bytes" +	"io" +	"sync" + +	"github.com/rfjakob/gocryptfs/internal/contentenc" +	"github.com/rfjakob/gocryptfs/internal/pathiv" +	"github.com/rfjakob/gocryptfs/internal/tlog" +) + +var inodeTable sync.Map + +// encryptBlocks - encrypt "plaintext" into a number of ciphertext blocks. +// "plaintext" must already be block-aligned. +func (rf *File) encryptBlocks(plaintext []byte, firstBlockNo uint64, fileID []byte, block0IV []byte) []byte { +	inBuf := bytes.NewBuffer(plaintext) +	var outBuf bytes.Buffer +	bs := int(rf.contentEnc.PlainBS()) +	for blockNo := firstBlockNo; inBuf.Len() > 0; blockNo++ { +		inBlock := inBuf.Next(bs) +		iv := pathiv.BlockIV(block0IV, blockNo) +		outBlock := rf.contentEnc.EncryptBlockNonce(inBlock, blockNo, fileID, iv) +		outBuf.Write(outBlock) +	} +	return outBuf.Bytes() +} + +// readBackingFile: read from the backing plaintext file, encrypt it, return the +// ciphertext. +// "off" ... ciphertext offset (must be >= HEADER_LEN) +// "length" ... ciphertext length +func (f *File) readBackingFile(off uint64, length uint64) (out []byte, err error) { +	blocks := f.contentEnc.ExplodeCipherRange(off, length) + +	// Read the backing plaintext in one go +	alignedOffset, alignedLength := contentenc.JointPlaintextRange(blocks) +	plaintext := make([]byte, int(alignedLength)) +	n, err := f.fd.ReadAt(plaintext, int64(alignedOffset)) +	if err != nil && err != io.EOF { +		tlog.Warn.Printf("readBackingFile: ReadAt: %s", err.Error()) +		return nil, err +	} +	// Truncate buffer down to actually read bytes +	plaintext = plaintext[0:n] + +	// Encrypt blocks +	ciphertext := f.encryptBlocks(plaintext, blocks[0].BlockNo, f.header.ID, f.block0IV) + +	// Crop down to the relevant part +	lenHave := len(ciphertext) +	skip := blocks[0].Skip +	endWant := int(skip + length) +	if lenHave > endWant { +		out = ciphertext[skip:endWant] +	} else if lenHave > int(skip) { +		out = ciphertext[skip:lenHave] +	} // else: out stays empty, file was smaller than the requested offset + +	return out, nil +} diff --git a/internal/fusefrontend_reverse/node.go b/internal/fusefrontend_reverse/node.go index 884a97e..deaf953 100644 --- a/internal/fusefrontend_reverse/node.go +++ b/internal/fusefrontend_reverse/node.go @@ -2,6 +2,8 @@ package fusefrontend_reverse  import (  	"context" +	"fmt" +	"os"  	"path/filepath"  	"syscall" @@ -10,8 +12,10 @@ import (  	"github.com/hanwen/go-fuse/v2/fs"  	"github.com/hanwen/go-fuse/v2/fuse" -	"github.com/rfjakob/gocryptfs/internal/configfile" +	"github.com/rfjakob/gocryptfs/internal/contentenc" +	"github.com/rfjakob/gocryptfs/internal/pathiv"  	"github.com/rfjakob/gocryptfs/internal/syscallcompat" +	"github.com/rfjakob/gocryptfs/internal/tlog"  )  // Node is a file or directory in the filesystem tree @@ -33,15 +37,7 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch  		return n.lookupLongnameName(ctx, cName, out)  	} else if t == typeConfig {  		// gocryptfs.conf -		var err error -		pName = configfile.ConfReverseName -		rn := n.rootNode() -		dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, "") -		if err != nil { -			errno = fs.ToErrno(err) -			return -		} -		defer syscall.Close(dirfd) +		return n.lookupConf(ctx, out)  	} else if t == typeReal {  		// real file  		dirfd, pName, errno = n.prepareAtSyscall(cName) @@ -112,3 +108,73 @@ func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {  	cName := filepath.Base(n.Path())  	return n.readlink(dirfd, cName, pName)  } + +// Open - FUSE call. Open already-existing file. +// +// Symlink-safe through Openat(). +func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { +	dirfd, pName, errno := n.prepareAtSyscall("") +	if errno != 0 { +		return +	} +	defer syscall.Close(dirfd) + +	fd, err := syscallcompat.Openat(dirfd, pName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0) +	if err != nil { +		errno = fs.ToErrno(err) +		return +	} + +	// Reject access if the file descriptor does not refer to a regular file. +	var st syscall.Stat_t +	err = syscall.Fstat(fd, &st) +	if err != nil { +		tlog.Warn.Printf("Open: Fstat error: %v", err) +		syscall.Close(fd) +		errno = fs.ToErrno(err) +		return +	} +	var a fuse.Attr +	a.FromStat(&st) +	if !a.IsRegular() { +		tlog.Warn.Printf("ino%d: newFile: not a regular file", st.Ino) +		syscall.Close(fd) +		errno = syscall.EACCES +		return +	} +	// See if we have that inode number already in the table +	// (even if Nlink has dropped to 1) +	var derivedIVs pathiv.FileIVs +	v, found := inodeTable.Load(st.Ino) +	if found { +		tlog.Debug.Printf("ino%d: newFile: found in the inode table", st.Ino) +		derivedIVs = v.(pathiv.FileIVs) +	} else { +		p := n.Path() +		derivedIVs = pathiv.DeriveFile(p) +		// Nlink > 1 means there is more than one path to this file. +		// Store the derived values so we always return the same data, +		// regardless of the path that is used to access the file. +		// This means that the first path wins. +		if st.Nlink > 1 { +			v, found = inodeTable.LoadOrStore(st.Ino, derivedIVs) +			if found { +				// Another thread has stored a different value before we could. +				derivedIVs = v.(pathiv.FileIVs) +			} else { +				tlog.Debug.Printf("ino%d: newFile: Nlink=%d, stored in the inode table", st.Ino, st.Nlink) +			} +		} +	} +	header := contentenc.FileHeader{ +		Version: contentenc.CurrentVersion, +		ID:      derivedIVs.ID, +	} +	fh = &File{ +		fd:         os.NewFile(uintptr(fd), fmt.Sprintf("fd%d", fd)), +		header:     header, +		block0IV:   derivedIVs.Block0IV, +		contentEnc: n.rootNode().contentEnc, +	} +	return +} diff --git a/internal/fusefrontend_reverse/node_api_check.go b/internal/fusefrontend_reverse/node_api_check.go index e926fc3..875c9b9 100644 --- a/internal/fusefrontend_reverse/node_api_check.go +++ b/internal/fusefrontend_reverse/node_api_check.go @@ -9,11 +9,10 @@ var _ = (fs.NodeGetattrer)((*Node)(nil))  var _ = (fs.NodeLookuper)((*Node)(nil))  var _ = (fs.NodeReaddirer)((*Node)(nil))  var _ = (fs.NodeReadlinker)((*Node)(nil)) - -/* TODO  var _ = (fs.NodeOpener)((*Node)(nil)) + +/*  var _ = (fs.NodeStatfser)((*Node)(nil)) -var _ = (fs.NodeMknoder)((*Node)(nil))  var _ = (fs.NodeGetxattrer)((*Node)(nil))  var _ = (fs.NodeListxattrer)((*Node)(nil))  */ @@ -23,6 +22,7 @@ var _ = (fs.NodeOpendirer)((*Node)(nil))  */  /* Will not implement these - reverse mode is read-only! +var _ = (fs.NodeMknoder)((*Node)(nil))  var _ = (fs.NodeCreater)((*Node)(nil))  var _ = (fs.NodeMkdirer)((*Node)(nil))  var _ = (fs.NodeRmdirer)((*Node)(nil)) diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go index 7669a17..fd0abfc 100644 --- a/internal/fusefrontend_reverse/node_helpers.go +++ b/internal/fusefrontend_reverse/node_helpers.go @@ -10,6 +10,7 @@ import (  	"github.com/hanwen/go-fuse/v2/fs"  	"github.com/hanwen/go-fuse/v2/fuse" +	"github.com/rfjakob/gocryptfs/internal/configfile"  	"github.com/rfjakob/gocryptfs/internal/pathiv"  	"github.com/rfjakob/gocryptfs/internal/syscallcompat"  ) @@ -115,8 +116,8 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus  		errno = fs.ToErrno(err)  		return  	} -	var vf *virtualFile -	vf, errno = n.newVirtualFile([]byte(cFullname), st, inoTagNameFile) +	var vf *VirtualMemNode +	vf, errno = n.newVirtualMemNode([]byte(cFullname), st, inoTagNameFile)  	if errno != 0 {  		return nil, errno  	} @@ -141,8 +142,8 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod  		return  	}  	content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV) -	var vf *virtualFile -	vf, errno = n.newVirtualFile(content, st, inoTagDirIV) +	var vf *VirtualMemNode +	vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)  	if errno != 0 {  		return nil, errno  	} @@ -153,6 +154,30 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod  	return  } +// lookupConf returns a new Inode for the gocryptfs.conf file +func (n *Node) lookupConf(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { +	rn := n.rootNode() +	p := filepath.Join(rn.args.Cipherdir, configfile.ConfReverseName) +	var st syscall.Stat_t +	err := syscall.Stat(p, &st) +	if err != nil { +		errno = fs.ToErrno(err) +		return +	} +	// Get unique inode number +	rn.inoMap.TranslateStat(&st) +	out.Attr.FromStat(&st) +	// Create child node +	id := fs.StableAttr{ +		Mode: uint32(st.Mode), +		Gen:  1, +		Ino:  st.Ino, +	} +	node := &VirtualConfNode{path: p} +	ch = n.NewInode(ctx, node, id) +	return +} +  // readlink reads and encrypts a symlink. Used by Readlink, Getattr, Lookup.  func (n *Node) readlink(dirfd int, cName string, pName string) (out []byte, errno syscall.Errno) {  	plainTarget, err := syscallcompat.Readlinkat(dirfd, pName) diff --git a/internal/fusefrontend_reverse/virtualconf.go b/internal/fusefrontend_reverse/virtualconf.go new file mode 100644 index 0000000..8620f6d --- /dev/null +++ b/internal/fusefrontend_reverse/virtualconf.go @@ -0,0 +1,55 @@ +package fusefrontend_reverse + +import ( +	"context" +	"sync" +	"syscall" + +	"github.com/hanwen/go-fuse/v2/fs" +	"github.com/hanwen/go-fuse/v2/fuse" +) + +var _ = (fs.NodeOpener)((*VirtualConfNode)(nil)) + +type VirtualConfNode struct { +	fs.Inode + +	path string +} + +func (n *VirtualConfNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { +	fd, err := syscall.Open(n.path, syscall.O_RDONLY, 0) +	if err != nil { +		errno = fs.ToErrno(err) +		return +	} +	fh = &VirtualConfFile{fd: fd} +	return +} + +// Check that we have implemented the fs.File* interfaces +var _ = (fs.FileReader)((*VirtualConfFile)(nil)) +var _ = (fs.FileReleaser)((*VirtualConfFile)(nil)) + +type VirtualConfFile struct { +	mu sync.Mutex +	fd int +} + +func (f *VirtualConfFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) { +	f.mu.Lock() +	defer f.mu.Unlock() +	res = fuse.ReadResultFd(uintptr(f.fd), off, len(buf)) +	return +} + +func (f *VirtualConfFile) Release(ctx context.Context) syscall.Errno { +	f.mu.Lock() +	defer f.mu.Unlock() +	if f.fd != -1 { +		err := syscall.Close(f.fd) +		f.fd = -1 +		return fs.ToErrno(err) +	} +	return syscall.EBADF +} diff --git a/internal/fusefrontend_reverse/virtualfile.go b/internal/fusefrontend_reverse/virtualnode.go index a7f6913..2ee9548 100644 --- a/internal/fusefrontend_reverse/virtualfile.go +++ b/internal/fusefrontend_reverse/virtualnode.go @@ -60,7 +60,9 @@ func (n *Node) lookupFileType(cName string) fileType {  	return typeReal  } -type virtualFile struct { +// VirtualMemNode is an in-memory node that does not have a representation +// on disk. +type VirtualMemNode struct {  	fs.Inode  	// file content @@ -69,12 +71,12 @@ type virtualFile struct {  	attr fuse.Attr  } -// newVirtualFile creates a new in-memory file that does not have a representation +// newVirtualMemNode creates a new in-memory file that does not have a representation  // on disk. "content" is the file content. Timestamps and file owner are copied  // from "parentFile" (file descriptor).  // For a "gocryptfs.diriv" file, you would use the parent directory as  // "parentFile". -func (n *Node) newVirtualFile(content []byte, parentStat *syscall.Stat_t, inoTag uint8) (vf *virtualFile, errno syscall.Errno) { +func (n *Node) newVirtualMemNode(content []byte, parentStat *syscall.Stat_t, inoTag uint8) (vf *VirtualMemNode, errno syscall.Errno) {  	if inoTag == 0 {  		log.Panicf("BUG: inoTag for virtual file is zero - this will cause ino collisions!")  	} @@ -90,23 +92,23 @@ func (n *Node) newVirtualFile(content []byte, parentStat *syscall.Stat_t, inoTag  	var a fuse.Attr  	a.FromStat(st) -	vf = &virtualFile{content: content, attr: a} +	vf = &VirtualMemNode{content: content, attr: a}  	return  }  // Open - FUSE call -func (f *virtualFile) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { +func (f *VirtualMemNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {  	return nil, fuse.FOPEN_KEEP_CACHE, 0  }  // GetAttr - FUSE call -func (f *virtualFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno { +func (f *VirtualMemNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {  	out.Attr = f.attr  	return 0  }  // Read - FUSE call -func (f *virtualFile) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { +func (f *VirtualMemNode) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {  	end := int(off) + len(dest)  	if end > len(f.content) {  		end = len(f.content) | 
