diff options
| author | Joseph Richey <joerichey@google.com> | 2019-10-23 22:11:25 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-10-23 22:11:25 -0700 |
| commit | f819c93ef40851ddad0470a711c673c643e73ca6 (patch) | |
| tree | f0111ce2660074b9eedb578cbeb42684858f602d /filesystem | |
| parent | 61464729e79d4b27a878718a92e4e3b70f7ad317 (diff) | |
| parent | 83828388b4195cb5b2de5dad937d19208c1fa7ff (diff) | |
Merge pull request #150 from ebiggers/allow-metadata-symlink
filesystem: allow .fscrypt to be a symlink
Diffstat (limited to 'filesystem')
| -rw-r--r-- | filesystem/filesystem.go | 29 | ||||
| -rw-r--r-- | filesystem/filesystem_test.go | 80 |
2 files changed, 103 insertions, 6 deletions
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go index 66b3804..a11f908 100644 --- a/filesystem/filesystem.go +++ b/filesystem/filesystem.go @@ -85,6 +85,10 @@ var ( // used when a Policy on filesystem A is protected with Protector on filesystem // B. In this scenario, we store a "link file" in the protectors directory whose // contents look like "UUID=3a6d9a76-47f0-4f13-81bf-3332fbe984fb". +// +// We also allow ".fscrypt" to be a symlink which was previously created. This +// allows login protectors to be created when the root filesystem is read-only, +// provided that "/.fscrypt" is a symlink pointing to a writable location. type Mount struct { Path string Filesystem string @@ -124,9 +128,21 @@ func (m *Mount) String() string { Device: %s`, m.Path, m.Filesystem, m.Options, m.Device) } -// BaseDir returns the path of the base fscrypt directory on this filesystem. +// BaseDir returns the path to the base fscrypt directory for this filesystem. func (m *Mount) BaseDir() string { - return filepath.Join(m.Path, baseDirName) + rawBaseDir := filepath.Join(m.Path, baseDirName) + // We allow the base directory to be a symlink, but some callers need + // the real path, so dereference the symlink here if needed. Since the + // directory the symlink points to may not exist yet, we have to read + // the symlink manually rather than use filepath.EvalSymlinks. + target, err := os.Readlink(rawBaseDir) + if err != nil { + return rawBaseDir // not a symlink + } + if filepath.IsAbs(target) { + return target + } + return filepath.Join(m.Path, target) } // ProtectorDir returns the directory containing the protector metadata. @@ -157,11 +173,12 @@ func (m *Mount) policyPath(descriptor string) string { return filepath.Join(m.PolicyDir(), descriptor) } -// tempMount creates a temporary Mount under the main directory. The path for -// the returned tempMount should be removed by the caller. +// tempMount creates a temporary directory alongside this Mount's base fscrypt +// directory and returns a temporary Mount which represents this temporary +// directory. The caller is responsible for removing this temporary directory. func (m *Mount) tempMount() (*Mount, error) { - trashDir, err := ioutil.TempDir(m.Path, tempPrefix) - return &Mount{Path: trashDir}, err + tempDir, err := ioutil.TempDir(filepath.Dir(m.BaseDir()), tempPrefix) + return &Mount{Path: tempDir}, err } // err modifies an error to contain the path of this filesystem. diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go index 2394f68..b85ead5 100644 --- a/filesystem/filesystem_test.go +++ b/filesystem/filesystem_test.go @@ -20,6 +20,8 @@ package filesystem import ( + "io/ioutil" + "log" "os" "path/filepath" "testing" @@ -109,6 +111,84 @@ 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) + return err == nil && info.Mode()&os.ModeSymlink != 0 +} + +// Test that when MOUNTPOINT/.fscrypt is a pre-created symlink, fscrypt will +// create/delete the metadata at the location pointed to by the symlink. +// +// This is a helper function that is called twice: once to test an absolute +// symlink and once to test a relative symlink. +func testSetupWithSymlink(t *testing.T, mnt *Mount, symlinkTarget string, realDir string) { + rawBaseDir := filepath.Join(mnt.Path, baseDirName) + if err := os.Symlink(symlinkTarget, rawBaseDir); err != nil { + t.Fatal(err) + } + defer os.Remove(rawBaseDir) + + if err := mnt.Setup(); err != nil { + t.Fatal(err) + } + defer mnt.RemoveAllMetadata() + if err := mnt.CheckSetup(); err != nil { + t.Fatal(err) + } + if !isSymlink(rawBaseDir) { + t.Fatal("base dir should still be a symlink") + } + if !isDir(realDir) { + t.Fatal("real base dir should exist") + } + if err := mnt.RemoveAllMetadata(); err != nil { + t.Fatal(err) + } + if !isSymlink(rawBaseDir) { + t.Fatal("base dir should still be a symlink") + } + if isDir(realDir) { + t.Fatal("real base dir should no longer exist") + } +} + +func TestSetupWithAbsoluteSymlink(t *testing.T) { + mnt, err := getTestMount(t) + if err != nil { + t.Fatal(err) + } + tempDir, err := ioutil.TempDir("", "fscrypt") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + realDir := filepath.Join(tempDir, "realDir") + if realDir, err = filepath.Abs(realDir); err != nil { + t.Fatal(err) + } + testSetupWithSymlink(t, mnt, realDir, realDir) +} + +func TestSetupWithRelativeSymlink(t *testing.T) { + mnt, err := getTestMount(t) + if err != nil { + t.Fatal(err) + } + realDir := filepath.Join(mnt.Path, ".fscrypt-real") + testSetupWithSymlink(t, mnt, ".fscrypt-real", realDir) +} + // Adding a good Protector should succeed, adding a bad one should fail func TestAddProtector(t *testing.T) { mnt, err := getSetupMount(t) |