diff options
Diffstat (limited to 'filesystem/filesystem_test.go')
| -rw-r--r-- | filesystem/filesystem_test.go | 297 |
1 files changed, 262 insertions, 35 deletions
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go index 92726b2..0e15256 100644 --- a/filesystem/filesystem_test.go +++ b/filesystem/filesystem_test.go @@ -21,12 +21,14 @@ package filesystem import ( "io/ioutil" - "log" "os" + "os/user" "path/filepath" + "syscall" "testing" "github.com/golang/protobuf/proto" + "golang.org/x/sys/unix" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/metadata" @@ -58,6 +60,19 @@ func getFakeProtector() *metadata.ProtectorData { } } +func getFakeLoginProtector(uid int64) *metadata.ProtectorData { + protector := getFakeProtector() + protector.Source = metadata.SourceType_pam_passphrase + protector.Uid = uid + protector.Costs = &metadata.HashingCosts{ + Time: 1, + Memory: 1 << 8, + Parallelism: 1, + } + protector.Salt = make([]byte, 16) + return protector +} + func getFakePolicy() *metadata.PolicyData { return &metadata.PolicyData{ KeyDescriptor: "0123456789abcdef", @@ -77,7 +92,7 @@ func getSetupMount(t *testing.T) (*Mount, error) { if err != nil { return nil, err } - return mnt, mnt.Setup() + return mnt, mnt.Setup(WorldWritable) } // Tests that the setup works and creates the correct files @@ -87,7 +102,7 @@ func TestSetup(t *testing.T) { t.Fatal(err) } - if err := mnt.CheckSetup(); err != nil { + if err := mnt.CheckSetup(nil); err != nil { t.Error(err) } @@ -110,16 +125,6 @@ func TestRemoveAllMetadata(t *testing.T) { } } -// loggedLstat runs os.Lstat (doesn't dereference trailing symlink), but it logs -// the error if lstat returns any error other than nil or IsNotExist. -func loggedLstat(name string) (os.FileInfo, error) { - info, err := os.Lstat(name) - if err != nil && !os.IsNotExist(err) { - log.Print(err) - } - return info, err -} - // isSymlink returns true if the path exists and is that of a symlink. func isSymlink(path string) bool { info, err := loggedLstat(path) @@ -138,11 +143,11 @@ func testSetupWithSymlink(t *testing.T, mnt *Mount, symlinkTarget string, realDi } defer os.Remove(rawBaseDir) - if err := mnt.Setup(); err != nil { + if err := mnt.Setup(WorldWritable); err != nil { t.Fatal(err) } defer mnt.RemoveAllMetadata() - if err := mnt.CheckSetup(); err != nil { + if err := mnt.CheckSetup(nil); err != nil { t.Fatal(err) } if !isSymlink(rawBaseDir) { @@ -188,6 +193,57 @@ func TestSetupWithRelativeSymlink(t *testing.T) { testSetupWithSymlink(t, mnt, ".fscrypt-real", realDir) } +func testSetupMode(t *testing.T, mnt *Mount, setupMode SetupMode, expectedPerms os.FileMode) { + mnt.RemoveAllMetadata() + if err := mnt.Setup(setupMode); err != nil { + t.Fatal(err) + } + dirNames := []string{"policies", "protectors"} + for _, dirName := range dirNames { + fi, err := os.Stat(filepath.Join(mnt.Path, ".fscrypt", dirName)) + if err != nil { + t.Fatal(err) + } + if fi.Mode()&(os.ModeSticky|0777) != expectedPerms { + t.Errorf("directory %s doesn't have permissions %o", dirName, expectedPerms) + } + } +} + +// Tests that the supported setup modes (WorldWritable and SingleUserWritable) +// work as intended. +func TestSetupModes(t *testing.T) { + mnt, err := getTestMount(t) + if err != nil { + t.Fatal(err) + } + defer mnt.RemoveAllMetadata() + testSetupMode(t, mnt, WorldWritable, os.ModeSticky|0777) + testSetupMode(t, mnt, SingleUserWritable, 0755) +} + +// Tests that fscrypt refuses to use metadata directories that are +// world-writable but don't have the sticky bit set. +func TestInsecurePermissions(t *testing.T) { + mnt, err := getTestMount(t) + if err != nil { + t.Fatal(err) + } + defer mnt.RemoveAllMetadata() + + if err = mnt.Setup(WorldWritable); err != nil { + t.Fatal(err) + } + if err = os.Chmod(mnt.PolicyDir(), 0777); err != nil { + t.Fatal(err) + } + defer os.Chmod(mnt.PolicyDir(), os.ModeSticky|0777) + err = mnt.CheckSetup(nil) + if _, ok := err.(*ErrInsecurePermissions); !ok { + t.Fatal("expected ErrInsecurePermissions") + } +} + // Adding a good Protector should succeed, adding a bad one should fail func TestAddProtector(t *testing.T) { mnt, err := getSetupMount(t) @@ -197,31 +253,31 @@ func TestAddProtector(t *testing.T) { defer mnt.RemoveAllMetadata() protector := getFakeProtector() - if err = mnt.AddProtector(protector); err != nil { + if err = mnt.AddProtector(protector, nil); err != nil { t.Error(err) } // Change the source to bad one, or one that requires hashing costs protector.Source = metadata.SourceType_default - if mnt.AddProtector(protector) == nil { + if mnt.AddProtector(protector, nil) == nil { t.Error("bad source for a descriptor should make metadata invalid") } protector.Source = metadata.SourceType_custom_passphrase - if mnt.AddProtector(protector) == nil { + if mnt.AddProtector(protector, nil) == nil { t.Error("protectors using passphrases should require hashing costs") } protector.Source = metadata.SourceType_raw_key // Use a bad wrapped key protector.WrappedKey = wrappedPolicyKey - if mnt.AddProtector(protector) == nil { + if mnt.AddProtector(protector, nil) == nil { t.Error("bad length for protector keys should make metadata invalid") } protector.WrappedKey = wrappedProtectorKey // Change the descriptor (to a bad length) protector.ProtectorDescriptor = "abcde" - if mnt.AddProtector(protector) == nil { + if mnt.AddProtector(protector, nil) == nil { t.Error("bad descriptor length should make metadata invalid") } @@ -236,32 +292,32 @@ func TestAddPolicy(t *testing.T) { defer mnt.RemoveAllMetadata() policy := getFakePolicy() - if err = mnt.AddPolicy(policy); err != nil { + if err = mnt.AddPolicy(policy, nil); err != nil { t.Error(err) } // Bad encryption options should make policy invalid policy.Options.Padding = 7 - if mnt.AddPolicy(policy) == nil { + if mnt.AddPolicy(policy, nil) == nil { t.Error("padding not a power of 2 should make metadata invalid") } policy.Options.Padding = 16 policy.Options.Filenames = metadata.EncryptionOptions_default - if mnt.AddPolicy(policy) == nil { + if mnt.AddPolicy(policy, nil) == nil { t.Error("encryption mode not set should make metadata invalid") } policy.Options.Filenames = metadata.EncryptionOptions_AES_256_CTS // Use a bad wrapped key policy.WrappedPolicyKeys[0].WrappedKey = wrappedProtectorKey - if mnt.AddPolicy(policy) == nil { + if mnt.AddPolicy(policy, nil) == nil { t.Error("bad length for policy keys should make metadata invalid") } policy.WrappedPolicyKeys[0].WrappedKey = wrappedPolicyKey // Change the descriptor (to a bad length) policy.KeyDescriptor = "abcde" - if mnt.AddPolicy(policy) == nil { + if mnt.AddPolicy(policy, nil) == nil { t.Error("bad descriptor length should make metadata invalid") } } @@ -275,11 +331,11 @@ func TestSetPolicy(t *testing.T) { defer mnt.RemoveAllMetadata() policy := getFakePolicy() - if err = mnt.AddPolicy(policy); err != nil { + if err = mnt.AddPolicy(policy, nil); err != nil { t.Fatal(err) } - realPolicy, err := mnt.GetPolicy(policy.KeyDescriptor) + realPolicy, err := mnt.GetPolicy(policy.KeyDescriptor, nil) if err != nil { t.Fatal(err) } @@ -299,11 +355,11 @@ func TestSetProtector(t *testing.T) { defer mnt.RemoveAllMetadata() protector := getFakeProtector() - if err = mnt.AddProtector(protector); err != nil { + if err = mnt.AddProtector(protector, nil); err != nil { t.Fatal(err) } - realProtector, err := mnt.GetRegularProtector(protector.ProtectorDescriptor) + realProtector, err := mnt.GetRegularProtector(protector.ProtectorDescriptor, nil) if err != nil { t.Fatal(err) } @@ -313,6 +369,85 @@ func TestSetProtector(t *testing.T) { } } +// Tests that a login protector whose embedded UID doesn't match the file owner +// is considered invalid. (Such a file could be created by a malicious user to +// try to confuse fscrypt into processing the wrong file.) +func TestSpoofedLoginProtector(t *testing.T) { + myUID := int64(os.Geteuid()) + badUID := myUID + 1 // anything different from myUID + mnt, err := getSetupMount(t) + if err != nil { + t.Fatal(err) + } + defer mnt.RemoveAllMetadata() + + // Control case: protector with matching UID should be accepted. + protector := getFakeLoginProtector(myUID) + if err = mnt.AddProtector(protector, nil); err != nil { + t.Fatal(err) + } + _, err = mnt.GetRegularProtector(protector.ProtectorDescriptor, nil) + if err != nil { + t.Fatal(err) + } + if err = mnt.RemoveProtector(protector.ProtectorDescriptor); err != nil { + t.Fatal(err) + } + + // The real test: protector with mismatching UID should rejected, + // *unless* the process running the tests (and hence the file owner) is + // root in which case it should be accepted. + protector = getFakeLoginProtector(badUID) + if err = mnt.AddProtector(protector, nil); err != nil { + t.Fatal(err) + } + _, err = mnt.GetRegularProtector(protector.ProtectorDescriptor, nil) + if myUID == 0 { + if err != nil { + t.Fatal(err) + } + } else { + if err == nil { + t.Fatal("reading protector with bad UID unexpectedly succeeded") + } + } +} + +// Tests that the fscrypt metadata files are given mode 0600. +func TestMetadataFileMode(t *testing.T) { + mnt, err := getSetupMount(t) + if err != nil { + t.Fatal(err) + } + defer mnt.RemoveAllMetadata() + + // Policy + policy := getFakePolicy() + if err = mnt.AddPolicy(policy, nil); err != nil { + t.Fatal(err) + } + fi, err := os.Stat(filepath.Join(mnt.Path, ".fscrypt/policies/", policy.KeyDescriptor)) + if err != nil { + t.Fatal(err) + } + if fi.Mode()&0777 != 0600 { + t.Error("Policy file has wrong mode") + } + + // Protector + protector := getFakeProtector() + if err = mnt.AddProtector(protector, nil); err != nil { + t.Fatal(err) + } + fi, err = os.Stat(filepath.Join(mnt.Path, ".fscrypt/protectors", protector.ProtectorDescriptor)) + if err != nil { + t.Fatal(err) + } + if fi.Mode()&0777 != 0600 { + t.Error("Protector file has wrong mode") + } +} + // Gets a setup mount and a fake second mount func getTwoSetupMounts(t *testing.T) (realMnt, fakeMnt *Mount, err error) { if realMnt, err = getSetupMount(t); err != nil { @@ -325,7 +460,7 @@ func getTwoSetupMounts(t *testing.T) (realMnt, fakeMnt *Mount, err error) { return } fakeMnt = &Mount{Path: fakeMountpoint, FilesystemType: realMnt.FilesystemType} - err = fakeMnt.Setup() + err = fakeMnt.Setup(WorldWritable) return } @@ -345,19 +480,19 @@ func TestLinkedProtector(t *testing.T) { // Add the protector to the first filesystem protector := getFakeProtector() - if err = realMnt.AddProtector(protector); err != nil { + if err = realMnt.AddProtector(protector, nil); err != nil { t.Fatal(err) } // Add the link to the second filesystem var isNewLink bool - if isNewLink, err = fakeMnt.AddLinkedProtector(protector.ProtectorDescriptor, realMnt); err != nil { + if isNewLink, err = fakeMnt.AddLinkedProtector(protector.ProtectorDescriptor, realMnt, nil, nil); err != nil { t.Fatal(err) } if !isNewLink { t.Fatal("Link was not new") } - if isNewLink, err = fakeMnt.AddLinkedProtector(protector.ProtectorDescriptor, realMnt); err != nil { + if isNewLink, err = fakeMnt.AddLinkedProtector(protector.ProtectorDescriptor, realMnt, nil, nil); err != nil { t.Fatal(err) } if isNewLink { @@ -365,12 +500,12 @@ func TestLinkedProtector(t *testing.T) { } // Get the protector though the second system - _, err = fakeMnt.GetRegularProtector(protector.ProtectorDescriptor) + _, err = fakeMnt.GetRegularProtector(protector.ProtectorDescriptor, nil) if _, ok := err.(*ErrProtectorNotFound); !ok { t.Fatal(err) } - retMnt, retProtector, err := fakeMnt.GetProtector(protector.ProtectorDescriptor) + retMnt, retProtector, err := fakeMnt.GetProtector(protector.ProtectorDescriptor, nil) if err != nil { t.Fatal(err) } @@ -382,3 +517,95 @@ func TestLinkedProtector(t *testing.T) { t.Errorf("protector %+v does not equal expected protector %+v", retProtector, protector) } } + +func createFile(path string, size int64) error { + if err := ioutil.WriteFile(path, []byte{}, 0600); err != nil { + return err + } + return os.Truncate(path, size) +} + +// Tests the readMetadataFileSafe() function. +func TestReadMetadataFileSafe(t *testing.T) { + currentUser, err := util.EffectiveUser() + otherUser := &user.User{Uid: "-1"} + if err != nil { + t.Fatal(err) + } + tempDir, err := ioutil.TempDir("", "fscrypt") + if err != nil { + t.Fatal(err) + } + filePath := filepath.Join(tempDir, "file") + defer os.RemoveAll(tempDir) + + // Good file (control case) + if err = createFile(filePath, 1000); err != nil { + t.Fatal(err) + } + _, owner, err := readMetadataFileSafe(filePath, nil) + if err != nil { + t.Fatal("failed to read file") + } + if owner != int64(os.Geteuid()) { + t.Fatal("got wrong owner") + } + // Also try it with the trustedUser argument set to the current user. + if _, _, err = readMetadataFileSafe(filePath, currentUser); err != nil { + t.Fatal("failed to read file") + } + os.Remove(filePath) + + // File owned by another user. We might not have permission to actually + // change the file's ownership, so we simulate this by passing in a bad + // value for the trustedUser argument. + if err = createFile(filePath, 1000); err != nil { + t.Fatal(err) + } + _, _, err = readMetadataFileSafe(filePath, otherUser) + if util.IsUserRoot() { + if err != nil { + t.Fatal("root-owned file didn't pass owner validation") + } + } else { + if err == nil { + t.Fatal("unexpectedly could read file owned by another user") + } + } + os.Remove(filePath) + + // Nonexistent file + _, _, err = readMetadataFileSafe(filePath, nil) + if !os.IsNotExist(err) { + t.Fatal("trying to read nonexistent file didn't fail with expected error") + } + + // Symlink + if err = os.Symlink("target", filePath); err != nil { + t.Fatal(err) + } + _, _, err = readMetadataFileSafe(filePath, nil) + if err.(*os.PathError).Err != syscall.ELOOP { + t.Fatal("trying to read symlink didn't fail with ELOOP") + } + os.Remove(filePath) + + // FIFO + if err = unix.Mkfifo(filePath, 0600); err != nil { + t.Fatal(err) + } + _, _, err = readMetadataFileSafe(filePath, nil) + if _, ok := err.(*ErrCorruptMetadata); !ok { + t.Fatal("trying to read FIFO didn't fail with expected error") + } + os.Remove(filePath) + + // Very large file + if err = createFile(filePath, 1000000); err != nil { + t.Fatal(err) + } + _, _, err = readMetadataFileSafe(filePath, nil) + if _, ok := err.(*ErrCorruptMetadata); !ok { + t.Fatal("trying to read very large file didn't fail with expected error") + } +} |