Skip to content

Commit 91aa3eb

Browse files
authored
Merge pull request #346 from google/fixes
Metadata validation and other security improvements
2 parents 1ab74f5 + 9770081 commit 91aa3eb

40 files changed

Lines changed: 1271 additions & 303 deletions

README.md

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies](#runtime-dependencies).
3737
- [Building and installing](#building-and-installing)
3838
- [Runtime dependencies](#runtime-dependencies)
3939
- [Configuration file](#configuration-file)
40+
- [Setting up `fscrypt` on a filesystem](#setting-up-fscrypt-on-a-filesystem)
4041
- [Setting up for login protectors](#setting-up-for-login-protectors)
4142
- [Securing your login passphrase](#securing-your-login-passphrase)
4243
- [Enabling the PAM module](#enabling-the-pam-module)
@@ -319,7 +320,8 @@ that looks like the following:
319320
"filenames": "AES_256_CTS",
320321
"policy_version": "2"
321322
},
322-
"use_fs_keyring_for_v1_policies": false
323+
"use_fs_keyring_for_v1_policies": false,
324+
"allow_cross_user_metadata": false
323325
}
324326
```
325327

@@ -377,6 +379,54 @@ The fields are:
377379
kernels, it's better to not use this setting and instead (re-)create your
378380
encrypted directories with `"policy_version": "2"`.
379381

382+
* "allow\_cross\_user\_metadata" specifies whether `fscrypt` will allow
383+
protectors and policies from other non-root users to be read, e.g. to be
384+
offered as options by `fscrypt encrypt`. The default value is `false`, since
385+
other users might be untrusted and could create malicious files. This can be
386+
set to `true` to restore the old behavior on systems where `fscrypt` metadata
387+
needs to be shared between multiple users. Note that this option is
388+
independent from the permissions on the metadata files themselves, which are
389+
set to 0600 by default; users who wish to share their metadata files with
390+
other users would also need to explicitly change their mode to 0644.
391+
392+
## Setting up `fscrypt` on a filesystem
393+
394+
`fscrypt` needs some directories to exist on the filesystem on which encryption
395+
will be used:
396+
397+
* `MOUNTPOINT/.fscrypt/policies`
398+
* `MOUNTPOINT/.fscrypt/protectors`
399+
400+
(If login protectors are used, these must also exist on the root filesystem.)
401+
402+
To create these directories, run `fscrypt setup MOUNTPOINT`. If MOUNTPOINT is
403+
owned by root, as is usually the case, then this command will require root.
404+
405+
There will be one decision you'll need to make: whether non-root users will be
406+
allowed to create `fscrypt` metadata (policies and protectors).
407+
408+
If you say `y`, then these directories will be made world-writable, with the
409+
sticky bit set so that users can't delete each other's files -- just like
410+
`/tmp`. If you say `N`, then these directories will be writable only by root.
411+
412+
Saying `y` maximizes the usability of `fscrypt`, and on most systems it's fine
413+
to say `y`. However, on some systems this may be inappropriate, as it will
414+
allow malicious users to fill the entire filesystem unless filesystem quotas
415+
have been configured -- similar to problems that have historically existed with
416+
other world-writable directories, e.g. `/tmp`. If you are concerned about this,
417+
say `N`. If you say `N`, then you'll only be able to run `fscrypt` as root to
418+
set up encryption on users' behalf, unless you manually set custom permissions
419+
on the metadata directories to grant write access to specific users or groups.
420+
421+
If you chose the wrong mode at `fscrypt setup` time, you can change the
422+
directory permissions at any time. To enable single-user writable mode, run:
423+
424+
sudo chmod 0755 MOUNTPOINT/.fscrypt/*
425+
426+
To enable world-writable mode, run:
427+
428+
sudo chmod 1777 MOUNTPOINT/.fscrypt/*
429+
380430
## Setting up for login protectors
381431

382432
If you want any encrypted directories to be protected by your login passphrase,
@@ -646,11 +696,15 @@ MOUNTPOINT DEVICE FILESYSTEM ENCRYPTION FSCRYPT
646696
Defaulting to policy_version 2 because kernel supports it.
647697
Customizing passphrase hashing difficulty for this system...
648698
Created global config file at "/etc/fscrypt.conf".
649-
Metadata directories created at "/.fscrypt".
699+
Allow users other than root to create fscrypt metadata on the root filesystem?
700+
(See https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem) [y/N] y
701+
Metadata directories created at "/.fscrypt", writable by everyone.
650702

651703
# Start using fscrypt with our filesystem
652-
>>>>> fscrypt setup /mnt/disk
653-
Metadata directories created at "/mnt/disk/.fscrypt".
704+
>>>>> sudo fscrypt setup /mnt/disk
705+
Allow users other than root to create fscrypt metadata on this filesystem? (See
706+
https://github.com/google/fscrypt#setting-up-fscrypt-on-a-filesystem) [y/N] y
707+
Metadata directories created at "/mnt/disk/.fscrypt", writable by everyone.
654708

655709
# Initialize encryption on a new empty directory
656710
>>>>> mkdir /mnt/disk/dir1
@@ -678,8 +732,8 @@ POLICY UNLOCKED PROTECTORS
678732

679733
#### Quiet version
680734
```bash
681-
>>>>> sudo fscrypt setup --quiet --force
682-
>>>>> fscrypt setup /mnt/disk --quiet
735+
>>>>> sudo fscrypt setup --quiet --force --all-users
736+
>>>>> sudo fscrypt setup /mnt/disk --quiet --all-users
683737
>>>>> echo "hunter2" | fscrypt encrypt /mnt/disk/dir1 --quiet --source=custom_passphrase --name="Super Secret"
684738
```
685739

actions/context.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ type Context struct {
5858
// the user for whom the keys are claimed in the filesystem keyring when
5959
// v2 policies are provisioned.
6060
TargetUser *user.User
61+
// TrustedUser is the user for whom policies and protectors are allowed
62+
// to be read. Specifically, if TrustedUser is set, then only
63+
// policies and protectors owned by TrustedUser or by root will be
64+
// allowed to be read. If it's nil, then all policies and protectors
65+
// the process has filesystem-level read access to will be allowed.
66+
TrustedUser *user.User
6167
}
6268

6369
// NewContextFromPath makes a context for the filesystem containing the
@@ -112,6 +118,16 @@ func newContextFromUser(targetUser *user.User) (*Context, error) {
112118
return nil, err
113119
}
114120

121+
// By default, when running as a non-root user we only read policies and
122+
// protectors owned by the user or root. When running as root, we allow
123+
// reading all policies and protectors.
124+
if !ctx.Config.GetAllowCrossUserMetadata() && !util.IsUserRoot() {
125+
ctx.TrustedUser, err = util.EffectiveUser()
126+
if err != nil {
127+
return nil, err
128+
}
129+
}
130+
115131
log.Printf("creating context for user %q", targetUser.Username)
116132
return ctx, nil
117133
}
@@ -122,7 +138,7 @@ func (ctx *Context) checkContext() error {
122138
if err := ctx.Config.CheckValidity(); err != nil {
123139
return &ErrBadConfig{ctx.Config, err}
124140
}
125-
return ctx.Mount.CheckSetup()
141+
return ctx.Mount.CheckSetup(ctx.TrustedUser)
126142
}
127143

128144
func (ctx *Context) getKeyringOptions() *keyring.Options {
@@ -136,7 +152,7 @@ func (ctx *Context) getKeyringOptions() *keyring.Options {
136152
// getProtectorOption returns the ProtectorOption for the protector on the
137153
// context's mountpoint with the specified descriptor.
138154
func (ctx *Context) getProtectorOption(protectorDescriptor string) *ProtectorOption {
139-
mnt, data, err := ctx.Mount.GetProtector(protectorDescriptor)
155+
mnt, data, err := ctx.Mount.GetProtector(protectorDescriptor, ctx.TrustedUser)
140156
if err != nil {
141157
return &ProtectorOption{ProtectorInfo{}, nil, err}
142158
}
@@ -155,7 +171,7 @@ func (ctx *Context) ProtectorOptions() ([]*ProtectorOption, error) {
155171
if err := ctx.checkContext(); err != nil {
156172
return nil, err
157173
}
158-
descriptors, err := ctx.Mount.ListProtectors()
174+
descriptors, err := ctx.Mount.ListProtectors(ctx.TrustedUser)
159175
if err != nil {
160176
return nil, err
161177
}

actions/context_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"testing"
2828
"time"
2929

30+
"github.com/google/fscrypt/filesystem"
3031
"github.com/google/fscrypt/util"
3132
"github.com/pkg/errors"
3233
)
@@ -67,7 +68,7 @@ func setupContext() (ctx *Context, err error) {
6768
return nil, err
6869
}
6970

70-
return ctx, ctx.Mount.Setup()
71+
return ctx, ctx.Mount.Setup(filesystem.WorldWritable)
7172
}
7273

7374
// Cleans up the testing config file and testing filesystem data.

actions/policy.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424
"log"
2525
"os"
26+
"os/user"
2627

2728
"github.com/golang/protobuf/proto"
2829
"github.com/pkg/errors"
@@ -145,7 +146,7 @@ func PurgeAllPolicies(ctx *Context) error {
145146
if err := ctx.checkContext(); err != nil {
146147
return err
147148
}
148-
policies, err := ctx.Mount.ListPolicies()
149+
policies, err := ctx.Mount.ListPolicies(nil)
149150
if err != nil {
150151
return err
151152
}
@@ -178,6 +179,7 @@ type Policy struct {
178179
data *metadata.PolicyData
179180
key *crypto.Key
180181
created bool
182+
ownerIfCreating *user.User
181183
newLinkedProtectors []string
182184
}
183185

@@ -210,6 +212,12 @@ func CreatePolicy(ctx *Context, protector *Protector) (*Policy, error) {
210212
created: true,
211213
}
212214

215+
policy.ownerIfCreating, err = getOwnerOfMetadataForProtector(protector)
216+
if err != nil {
217+
policy.Lock()
218+
return nil, err
219+
}
220+
213221
if err = policy.AddProtector(protector); err != nil {
214222
policy.Lock()
215223
return nil, err
@@ -225,7 +233,7 @@ func GetPolicy(ctx *Context, descriptor string) (*Policy, error) {
225233
if err := ctx.checkContext(); err != nil {
226234
return nil, err
227235
}
228-
data, err := ctx.Mount.GetPolicy(descriptor)
236+
data, err := ctx.Mount.GetPolicy(descriptor, ctx.TrustedUser)
229237
if err != nil {
230238
return nil, err
231239
}
@@ -262,7 +270,7 @@ func GetPolicyFromPath(ctx *Context, path string) (*Policy, error) {
262270
descriptor := pathData.KeyDescriptor
263271
log.Printf("found policy %s for %q", descriptor, path)
264272

265-
mountData, err := ctx.Mount.GetPolicy(descriptor)
273+
mountData, err := ctx.Mount.GetPolicy(descriptor, ctx.TrustedUser)
266274
if err != nil {
267275
log.Printf("getting policy metadata: %v", err)
268276
if _, ok := err.(*filesystem.ErrPolicyNotFound); ok {
@@ -410,6 +418,25 @@ func (policy *Policy) UsesProtector(protector *Protector) bool {
410418
return ok
411419
}
412420

421+
// getOwnerOfMetadataForProtector returns the User to whom the owner of any new
422+
// policies or protector links for the given protector should be set.
423+
//
424+
// This will return a non-nil value only when the protector is a login protector
425+
// and the process is running as root. In this scenario, root is setting up
426+
// encryption on the user's behalf, so we need to make new policies and
427+
// protector links owned by the user (rather than root) to allow them to be read
428+
// by the user, just like the login protector itself which is handled elsewhere.
429+
func getOwnerOfMetadataForProtector(protector *Protector) (*user.User, error) {
430+
if protector.data.Source == metadata.SourceType_pam_passphrase && util.IsUserRoot() {
431+
owner, err := util.UserFromUID(protector.data.Uid)
432+
if err != nil {
433+
return nil, err
434+
}
435+
return owner, nil
436+
}
437+
return nil, nil
438+
}
439+
413440
// AddProtector updates the data that is wrapping the Policy Key so that the
414441
// provided Protector is now protecting the specified Policy. If an error is
415442
// returned, no data has been changed. If the policy and protector are on
@@ -427,8 +454,13 @@ func (policy *Policy) AddProtector(protector *Protector) error {
427454
// to it on the policy's filesystem.
428455
if policy.Context.Mount != protector.Context.Mount {
429456
log.Printf("policy on %s\n protector on %s\n", policy.Context.Mount, protector.Context.Mount)
457+
ownerIfCreating, err := getOwnerOfMetadataForProtector(protector)
458+
if err != nil {
459+
return err
460+
}
430461
isNewLink, err := policy.Context.Mount.AddLinkedProtector(
431-
protector.Descriptor(), protector.Context.Mount)
462+
protector.Descriptor(), protector.Context.Mount,
463+
protector.Context.TrustedUser, ownerIfCreating)
432464
if err != nil {
433465
return err
434466
}
@@ -554,7 +586,7 @@ func (policy *Policy) CanBeAppliedWithoutProvisioning() bool {
554586

555587
// commitData writes the Policy's current data to the filesystem.
556588
func (policy *Policy) commitData() error {
557-
return policy.Context.Mount.AddPolicy(policy.data)
589+
return policy.Context.Mount.AddPolicy(policy.data, policy.ownerIfCreating)
558590
}
559591

560592
// findWrappedPolicyKey returns the index of the wrapped policy key

actions/policy_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727

2828
// Makes a protector and policy
2929
func makeBoth() (*Protector, *Policy, error) {
30-
protector, err := CreateProtector(testContext, testProtectorName, goodCallback)
30+
protector, err := CreateProtector(testContext, testProtectorName, goodCallback, nil)
3131
if err != nil {
3232
return nil, nil, err
3333
}
@@ -68,7 +68,7 @@ func TestPolicyGoodAddProtector(t *testing.T) {
6868
t.Fatal(err)
6969
}
7070

71-
pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback)
71+
pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback, nil)
7272
if err != nil {
7373
t.Fatal(err)
7474
}
@@ -103,7 +103,7 @@ func TestPolicyGoodRemoveProtector(t *testing.T) {
103103
t.Fatal(err)
104104
}
105105

106-
pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback)
106+
pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback, nil)
107107
if err != nil {
108108
t.Fatal(err)
109109
}
@@ -129,7 +129,7 @@ func TestPolicyBadRemoveProtector(t *testing.T) {
129129
t.Fatal(err)
130130
}
131131

132-
pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback)
132+
pro2, err := CreateProtector(testContext, testProtectorName2, goodCallback, nil)
133133
if err != nil {
134134
t.Fatal(err)
135135
}

actions/protector.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,18 @@ func checkIfUserHasLoginProtector(ctx *Context, uid int64) error {
109109
// to unlock policies and create new polices. As with the key struct, a
110110
// Protector should be wiped after use.
111111
type Protector struct {
112-
Context *Context
113-
data *metadata.ProtectorData
114-
key *crypto.Key
115-
created bool
112+
Context *Context
113+
data *metadata.ProtectorData
114+
key *crypto.Key
115+
created bool
116+
ownerIfCreating *user.User
116117
}
117118

118119
// CreateProtector creates an unlocked protector with a given name (name only
119120
// needed for custom and raw protector types). The keyFn provided to create the
120121
// Protector key will only be called once. If an error is returned, no data has
121122
// been changed on the filesystem.
122-
func CreateProtector(ctx *Context, name string, keyFn KeyFunc) (*Protector, error) {
123+
func CreateProtector(ctx *Context, name string, keyFn KeyFunc, owner *user.User) (*Protector, error) {
123124
if err := ctx.checkContext(); err != nil {
124125
return nil, err
125126
}
@@ -147,7 +148,8 @@ func CreateProtector(ctx *Context, name string, keyFn KeyFunc) (*Protector, erro
147148
Name: name,
148149
Source: ctx.Config.Source,
149150
},
150-
created: true,
151+
created: true,
152+
ownerIfCreating: owner,
151153
}
152154

153155
// Extra data is needed for some SourceTypes
@@ -199,7 +201,7 @@ func GetProtector(ctx *Context, descriptor string) (*Protector, error) {
199201
}
200202

201203
protector := &Protector{Context: ctx}
202-
protector.data, err = ctx.Mount.GetRegularProtector(descriptor)
204+
protector.data, err = ctx.Mount.GetRegularProtector(descriptor, ctx.TrustedUser)
203205
return protector, err
204206
}
205207

@@ -218,7 +220,7 @@ func GetProtectorFromOption(ctx *Context, option *ProtectorOption) (*Protector,
218220

219221
// Replace the context if this is a linked protector
220222
if option.LinkedMount != nil {
221-
ctx = &Context{ctx.Config, option.LinkedMount, ctx.TargetUser}
223+
ctx = &Context{ctx.Config, option.LinkedMount, ctx.TargetUser, ctx.TrustedUser}
222224
}
223225
return &Protector{Context: ctx, data: option.data}, nil
224226
}
@@ -294,5 +296,5 @@ func (protector *Protector) Rewrap(keyFn KeyFunc) error {
294296
return err
295297
}
296298

297-
return protector.Context.Mount.AddProtector(protector.data)
299+
return protector.Context.Mount.AddProtector(protector.data, protector.ownerIfCreating)
298300
}

actions/protector_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func badCallback(info ProtectorInfo, retry bool) (*crypto.Key, error) {
4343

4444
// Tests that we can create a valid protector.
4545
func TestCreateProtector(t *testing.T) {
46-
p, err := CreateProtector(testContext, testProtectorName, goodCallback)
46+
p, err := CreateProtector(testContext, testProtectorName, goodCallback, nil)
4747
if err != nil {
4848
t.Error(err)
4949
} else {
@@ -54,7 +54,7 @@ func TestCreateProtector(t *testing.T) {
5454

5555
// Tests that a failure in the callback is relayed back to the caller.
5656
func TestBadCallback(t *testing.T) {
57-
p, err := CreateProtector(testContext, testProtectorName, badCallback)
57+
p, err := CreateProtector(testContext, testProtectorName, badCallback, nil)
5858
if err == nil {
5959
p.Lock()
6060
p.Destroy()

0 commit comments

Comments
 (0)