aboutsummaryrefslogtreecommitdiff
path: root/internal/fusefrontend/node_xattr.go
blob: 09ee5ef5b4a542b7272553a3782e786f6e15074c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Package fusefrontend interfaces directly with the go-fuse library.
package fusefrontend

import (
	"bytes"
	"context"
	"strings"
	"syscall"

	"github.com/hanwen/go-fuse/v2/fuse"

	"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)

// -1 as uint32
const minus1 = ^uint32(0)

// xattr names are encrypted like file names, but with a fixed IV.
// Padded with "_xx" for length 16.
var xattrNameIV = []byte("xattr_name_iv_xx")

// We store encrypted xattrs under this prefix plus the base64-encoded
// encrypted original name.
var xattrStorePrefix = "user.gocryptfs."

// We get one read of this xattr for each write -
// see https://github.com/rfjakob/gocryptfs/issues/515 for details.
var xattrCapability = "security.capability"

// isAcl returns true if the attribute name is for storing ACLs
//
// ACLs are passed through without encryption
func isAcl(attr string) bool {
	return attr == "system.posix_acl_access" || attr == "system.posix_acl_default"
}

// GetXAttr - FUSE call. Reads the value of extended attribute "attr".
//
// This function is symlink-safe through Fgetxattr.
func (n *Node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
	rn := n.rootNode()
	// If we are not mounted with -suid, reading the capability xattr does not
	// make a lot of sense, so reject the request and gain a massive speedup.
	// See https://github.com/rfjakob/gocryptfs/issues/515 .
	if !rn.args.Suid && attr == xattrCapability {
		// Returning EOPNOTSUPP is what we did till
		// ca9e912a28b901387e1dbb85f6c531119f2d5ef2 "fusefrontend: drop xattr user namespace restriction"
		// and it did not cause trouble. Seems cleaner than saying ENODATA.
		return 0, syscall.EOPNOTSUPP
	}
	var data []byte
	// ACLs are passed through without encryption
	if isAcl(attr) {
		var errno syscall.Errno
		data, errno = n.getXAttr(attr)
		if errno != 0 {
			return minus1, errno
		}
	} else {
		// encrypted user xattr
		cAttr, err := rn.encryptXattrName(attr)
		if err != nil {
			return minus1, syscall.EIO
		}
		cData, errno := n.getXAttr(cAttr)
		if errno != 0 {
			return 0, errno
		}
		data, err = rn.decryptXattrValue(cData)
		if err != nil {
			tlog.Warn.Printf("GetXAttr: %v", err)
			return minus1, syscall.EIO
		}
	}
	// Caller passes size zero to find out how large their buffer should be
	if len(dest) == 0 {
		return uint32(len(data)), 0
	}
	if len(dest) < len(data) {
		return minus1, syscall.ERANGE
	}
	l := copy(dest, data)
	return uint32(l), 0
}

// SetXAttr - FUSE call. Set extended attribute.
//
// This function is symlink-safe through Fsetxattr.
func (n *Node) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
	rn := n.rootNode()
	flags = uint32(filterXattrSetFlags(int(flags)))

	// ACLs are passed through without encryption
	if isAcl(attr) {
		// result of setting an acl depends on the user doing it
		var context *fuse.Context
		if rn.args.PreserveOwner {
			context = toFuseCtx(ctx)
		}
		return n.setXAttr(context, attr, data, flags)
	}

	cAttr, err := rn.encryptXattrName(attr)
	if err != nil {
		return syscall.EINVAL
	}
	cData := rn.encryptXattrValue(data)
	return n.setXAttr(nil, cAttr, cData, flags)
}

// RemoveXAttr - FUSE call.
//
// This function is symlink-safe through Fremovexattr.
func (n *Node) Removexattr(ctx context.Context, attr string) syscall.Errno {
	rn := n.rootNode()

	// ACLs are passed through without encryption
	if isAcl(attr) {
		return n.removeXAttr(attr)
	}

	cAttr, err := rn.encryptXattrName(attr)
	if err != nil {
		return syscall.EINVAL
	}
	return n.removeXAttr(cAttr)
}

// ListXAttr - FUSE call. Lists extended attributes on the file at "relPath".
//
// This function is symlink-safe through Flistxattr.
func (n *Node) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
	cNames, errno := n.listXAttr()
	if errno != 0 {
		return 0, errno
	}
	rn := n.rootNode()
	var buf bytes.Buffer
	for _, curName := range cNames {
		// ACLs are passed through without encryption
		if isAcl(curName) {
			buf.WriteString(curName + "\000")
			continue
		}
		if !strings.HasPrefix(curName, xattrStorePrefix) {
			continue
		}
		name, err := rn.decryptXattrName(curName)
		if err != nil {
			tlog.Warn.Printf("ListXAttr: invalid xattr name %q: %v", curName, err)
			rn.reportMitigatedCorruption(curName)
			continue
		}
		// We *used to* encrypt ACLs, which caused a lot of problems.
		if isAcl(name) {
			tlog.Warn.Printf("ListXAttr: ignoring deprecated encrypted ACL %q = %q", curName, name)
			rn.reportMitigatedCorruption(curName)
			continue
		}
		buf.WriteString(name + "\000")
	}
	// Caller passes size zero to find out how large their buffer should be
	if len(dest) == 0 {
		return uint32(buf.Len()), 0
	}
	if buf.Len() > len(dest) {
		return minus1, syscall.ERANGE
	}
	return uint32(copy(dest, buf.Bytes())), 0
}