Skip to content

Commit

Permalink
Merge db31d21 into 6146472
Browse files Browse the repository at this point in the history
  • Loading branch information
ebiggers committed Oct 1, 2019
2 parents 6146472 + db31d21 commit ed7f066
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 6 deletions.
29 changes: 23 additions & 6 deletions filesystem/filesystem.go
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
63 changes: 63 additions & 0 deletions filesystem/filesystem_test.go
Expand Up @@ -20,6 +20,7 @@
package filesystem

import (
"io/ioutil"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -109,6 +110,68 @@ func TestRemoveAllMetadata(t *testing.T) {
}
}

// 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)
Expand Down
16 changes: 16 additions & 0 deletions filesystem/path.go
Expand Up @@ -56,6 +56,16 @@ func loggedStat(name string) (os.FileInfo, error) {
return info, err
}

// 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
}

// isDir returns true if the path exists and is that of a directory.
func isDir(path string) bool {
info, err := loggedStat(path)
Expand All @@ -68,6 +78,12 @@ func isDevice(path string) bool {
return err == nil && info.Mode()&os.ModeDevice != 0
}

// 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
}

// isDirCheckPerm returns true if the path exists and is a directory. If the
// specified permissions and sticky bit of mode do not match the path, an error
// is logged.
Expand Down

0 comments on commit ed7f066

Please sign in to comment.