Skip to content

Commit 411b4f9

Browse files
committed
Xattrs support in reverse mode
Fixes #827
1 parent 8689105 commit 411b4f9

File tree

7 files changed

+216
-23
lines changed

7 files changed

+216
-23
lines changed

internal/fusefrontend/node_xattr.go

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import (
1212
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
1313
)
1414

15-
// -1 as uint32
16-
const minus1 = ^uint32(0)
17-
1815
// We store encrypted xattrs under this prefix plus the base64-encoded
1916
// encrypted original name.
2017
var xattrStorePrefix = "user.gocryptfs."
@@ -50,13 +47,13 @@ func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32,
5047
var errno syscall.Errno
5148
data, errno = n.getXAttr(attr)
5249
if errno != 0 {
53-
return minus1, errno
50+
return 0, errno
5451
}
5552
} else {
5653
// encrypted user xattr
5754
cAttr, err := rn.encryptXattrName(attr)
5855
if err != nil {
59-
return minus1, syscall.EIO
56+
return 0, syscall.EIO
6057
}
6158
cData, errno := n.getXAttr(cAttr)
6259
if errno != 0 {
@@ -65,15 +62,11 @@ func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32,
6562
data, err = rn.decryptXattrValue(cData)
6663
if err != nil {
6764
tlog.Warn.Printf("GetXAttr: %v", err)
68-
return minus1, syscall.EIO
65+
return 0, syscall.EIO
6966
}
7067
}
71-
// Caller passes size zero to find out how large their buffer should be
72-
if len(dest) == 0 {
73-
return uint32(len(data)), 0
74-
}
7568
if len(dest) < len(data) {
76-
return minus1, syscall.ERANGE
69+
return uint32(len(data)), syscall.ERANGE
7770
}
7871
l := copy(dest, data)
7972
return uint32(l), 0
@@ -155,12 +148,8 @@ func (n *Node) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errn
155148
}
156149
buf.WriteString(name + "\000")
157150
}
158-
// Caller passes size zero to find out how large their buffer should be
159-
if len(dest) == 0 {
160-
return uint32(buf.Len()), 0
161-
}
162151
if buf.Len() > len(dest) {
163-
return minus1, syscall.ERANGE
152+
return uint32(buf.Len()), syscall.ERANGE
164153
}
165154
return uint32(copy(dest, buf.Bytes())), 0
166155
}

internal/fusefrontend_reverse/node_api_check.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,8 @@ var _ = (fs.NodeReaddirer)((*Node)(nil))
1111
var _ = (fs.NodeReadlinker)((*Node)(nil))
1212
var _ = (fs.NodeOpener)((*Node)(nil))
1313
var _ = (fs.NodeStatfser)((*Node)(nil))
14-
15-
/*
16-
TODO but low prio. reverse mode in gocryptfs v1 did not have xattr support
17-
either.
18-
1914
var _ = (fs.NodeGetxattrer)((*Node)(nil))
2015
var _ = (fs.NodeListxattrer)((*Node)(nil))
21-
*/
2216

2317
/* Not needed
2418
var _ = (fs.NodeOpendirer)((*Node)(nil))
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Package fusefrontend_reverse interfaces directly with the go-fuse library.
2+
package fusefrontend_reverse
3+
4+
import (
5+
"bytes"
6+
"context"
7+
"syscall"
8+
9+
"github.com/rfjakob/gocryptfs/v2/internal/pathiv"
10+
)
11+
12+
// We store encrypted xattrs under this prefix plus the base64-encoded
13+
// encrypted original name.
14+
var xattrStorePrefix = "user.gocryptfs."
15+
16+
// isAcl returns true if the attribute name is for storing ACLs
17+
//
18+
// ACLs are passed through without encryption
19+
func isAcl(attr string) bool {
20+
return attr == "system.posix_acl_access" || attr == "system.posix_acl_default"
21+
}
22+
23+
// GetXAttr - FUSE call. Reads the value of extended attribute "attr".
24+
//
25+
// This function is symlink-safe through Fgetxattr.
26+
func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
27+
rn := n.rootNode()
28+
var data []byte
29+
// ACLs are passed through without encryption
30+
if isAcl(attr) {
31+
var errno syscall.Errno
32+
data, errno = n.getXAttr(attr)
33+
if errno != 0 {
34+
return 0, errno
35+
}
36+
} else {
37+
pAttr, err := rn.decryptXattrName(attr)
38+
if err != nil {
39+
return 0, syscall.EINVAL
40+
}
41+
pData, errno := n.getXAttr(pAttr)
42+
if errno != 0 {
43+
return 0, errno
44+
}
45+
nonce := pathiv.Derive(n.Path()+"\000"+attr, pathiv.PurposeXattrIV)
46+
data = rn.encryptXattrValue(pData, nonce)
47+
}
48+
if len(dest) < len(data) {
49+
return uint32(len(data)), syscall.ERANGE
50+
}
51+
l := copy(dest, data)
52+
return uint32(l), 0
53+
}
54+
55+
// ListXAttr - FUSE call. Lists extended attributes on the file at "relPath".
56+
//
57+
// This function is symlink-safe through Flistxattr.
58+
func (n *Node) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
59+
pNames, errno := n.listXAttr()
60+
if errno != 0 {
61+
return 0, errno
62+
}
63+
rn := n.rootNode()
64+
var buf bytes.Buffer
65+
for _, pName := range pNames {
66+
// ACLs are passed through without encryption
67+
if isAcl(pName) {
68+
buf.WriteString(pName + "\000")
69+
continue
70+
}
71+
cName, err := rn.encryptXattrName(pName)
72+
if err != nil {
73+
continue
74+
}
75+
buf.WriteString(cName + "\000")
76+
}
77+
if buf.Len() > len(dest) {
78+
return uint32(buf.Len()), syscall.ERANGE
79+
}
80+
return uint32(copy(dest, buf.Bytes())), 0
81+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package fusefrontend_reverse
2+
3+
import (
4+
"syscall"
5+
6+
"github.com/hanwen/go-fuse/v2/fs"
7+
8+
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
9+
)
10+
11+
func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
12+
d, errno := n.prepareAtSyscall("")
13+
if errno != 0 {
14+
return
15+
}
16+
defer syscall.Close(d.dirfd)
17+
18+
// O_NONBLOCK to not block on FIFOs.
19+
fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
20+
if err != nil {
21+
return nil, fs.ToErrno(err)
22+
}
23+
defer syscall.Close(fd)
24+
25+
cData, err := syscallcompat.Fgetxattr(fd, cAttr)
26+
if err != nil {
27+
return nil, fs.ToErrno(err)
28+
}
29+
30+
return cData, 0
31+
}
32+
33+
func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
34+
d, errno := n.prepareAtSyscall("")
35+
if errno != 0 {
36+
return
37+
}
38+
defer syscall.Close(d.dirfd)
39+
40+
// O_NONBLOCK to not block on FIFOs.
41+
fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
42+
if err != nil {
43+
return nil, fs.ToErrno(err)
44+
}
45+
defer syscall.Close(fd)
46+
47+
pNames, err := syscallcompat.Flistxattr(fd)
48+
if err != nil {
49+
return nil, fs.ToErrno(err)
50+
}
51+
return pNames, 0
52+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package fusefrontend_reverse
2+
3+
import (
4+
"fmt"
5+
"syscall"
6+
7+
"github.com/hanwen/go-fuse/v2/fs"
8+
9+
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
10+
)
11+
12+
func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
13+
d, errno := n.prepareAtSyscall("")
14+
if errno != 0 {
15+
return
16+
}
17+
defer syscall.Close(d.dirfd)
18+
19+
procPath := fmt.Sprintf("/proc/self/fd/%d/%s", d.dirfd, d.pName)
20+
pData, err := syscallcompat.Lgetxattr(procPath, cAttr)
21+
if err != nil {
22+
return nil, fs.ToErrno(err)
23+
}
24+
return pData, 0
25+
}
26+
27+
func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
28+
d, errno := n.prepareAtSyscall("")
29+
if errno != 0 {
30+
return
31+
}
32+
defer syscall.Close(d.dirfd)
33+
34+
procPath := fmt.Sprintf("/proc/self/fd/%d/%s", d.dirfd, d.pName)
35+
pNames, err := syscallcompat.Llistxattr(procPath)
36+
if err != nil {
37+
return nil, fs.ToErrno(err)
38+
}
39+
return pNames, 0
40+
}

internal/fusefrontend_reverse/root_node.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
2222
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
2323

24-
"github.com/sabhiram/go-gitignore"
24+
ignore "github.com/sabhiram/go-gitignore"
2525
)
2626

2727
// RootNode is the root directory in a `gocryptfs -reverse` mount
@@ -182,3 +182,38 @@ func (rn *RootNode) uniqueStableAttr(mode uint32, ino uint64) fs.StableAttr {
182182
func (rn *RootNode) RootIno() uint64 {
183183
return rn.rootIno
184184
}
185+
186+
// encryptXattrValue encrypts the xattr value "data".
187+
// The data is encrypted like a file content block, but without binding it to
188+
// a file location (block number and file id are set to zero).
189+
// Special case: an empty value is encrypted to an empty value.
190+
func (rn *RootNode) encryptXattrValue(data []byte, nonce []byte) (cData []byte) {
191+
if len(data) == 0 {
192+
return []byte{}
193+
}
194+
return rn.contentEnc.EncryptBlockNonce(data, 0, nil, nonce)
195+
}
196+
197+
// encryptXattrName transforms "user.foo" to "user.gocryptfs.a5sAd4XAa47f5as6dAf"
198+
func (rn *RootNode) encryptXattrName(attr string) (string, error) {
199+
// xattr names are encrypted like file names, but with a fixed IV.
200+
cAttr, err := rn.nameTransform.EncryptXattrName(attr)
201+
if err != nil {
202+
return "", err
203+
}
204+
return xattrStorePrefix + cAttr, nil
205+
}
206+
207+
func (rn *RootNode) decryptXattrName(cAttr string) (attr string, err error) {
208+
// Reject anything that does not start with "user.gocryptfs."
209+
if !strings.HasPrefix(cAttr, xattrStorePrefix) {
210+
return "", syscall.EINVAL
211+
}
212+
// Strip "user.gocryptfs." prefix
213+
cAttr = cAttr[len(xattrStorePrefix):]
214+
attr, err = rn.nameTransform.DecryptXattrName(cAttr)
215+
if err != nil {
216+
return "", err
217+
}
218+
return attr, nil
219+
}

internal/pathiv/pathiv.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const (
2020
PurposeSymlinkIV Purpose = "SYMLINKIV"
2121
// PurposeBlock0IV means the value will be used as the IV of ciphertext block #0.
2222
PurposeBlock0IV Purpose = "BLOCK0IV"
23+
// PurposeXattrIV means the value will be used as a xattr IV
24+
PurposeXattrIV Purpose = "XATTRIV"
2325
)
2426

2527
// Derive derives an IV from an encrypted path by hashing it with sha256

0 commit comments

Comments
 (0)