Codebase list fscrypt / db31d21
filesystem: allow .fscrypt to be a symlink Support the case where the user has a read-only root filesystem (e.g. with OSTree) and had previously created a symlink /.fscrypt pointing to a writable location, so that login protectors can be created there. Resolves https://github.com/google/fscrypt/issues/131 Eric Biggers 4 years ago
3 changed file(s) with 102 addition(s) and 6 deletion(s). Raw diff Collapse all Expand all
8484 // used when a Policy on filesystem A is protected with Protector on filesystem
8585 // B. In this scenario, we store a "link file" in the protectors directory whose
8686 // contents look like "UUID=3a6d9a76-47f0-4f13-81bf-3332fbe984fb".
87 //
88 // We also allow ".fscrypt" to be a symlink which was previously created. This
89 // allows login protectors to be created when the root filesystem is read-only,
90 // provided that "/.fscrypt" is a symlink pointing to a writable location.
8791 type Mount struct {
8892 Path string
8993 Filesystem string
123127 Device: %s`, m.Path, m.Filesystem, m.Options, m.Device)
124128 }
125129
126 // BaseDir returns the path of the base fscrypt directory on this filesystem.
130 // BaseDir returns the path to the base fscrypt directory for this filesystem.
127131 func (m *Mount) BaseDir() string {
128 return filepath.Join(m.Path, baseDirName)
132 rawBaseDir := filepath.Join(m.Path, baseDirName)
133 // We allow the base directory to be a symlink, but some callers need
134 // the real path, so dereference the symlink here if needed. Since the
135 // directory the symlink points to may not exist yet, we have to read
136 // the symlink manually rather than use filepath.EvalSymlinks.
137 target, err := os.Readlink(rawBaseDir)
138 if err != nil {
139 return rawBaseDir // not a symlink
140 }
141 if filepath.IsAbs(target) {
142 return target
143 }
144 return filepath.Join(m.Path, target)
129145 }
130146
131147 // ProtectorDir returns the directory containing the protector metadata.
156172 return filepath.Join(m.PolicyDir(), descriptor)
157173 }
158174
159 // tempMount creates a temporary Mount under the main directory. The path for
160 // the returned tempMount should be removed by the caller.
175 // tempMount creates a temporary directory alongside this Mount's base fscrypt
176 // directory and returns a temporary Mount which represents this temporary
177 // directory. The caller is responsible for removing this temporary directory.
161178 func (m *Mount) tempMount() (*Mount, error) {
162 trashDir, err := ioutil.TempDir(m.Path, tempPrefix)
163 return &Mount{Path: trashDir}, err
179 tempDir, err := ioutil.TempDir(filepath.Dir(m.BaseDir()), tempPrefix)
180 return &Mount{Path: tempDir}, err
164181 }
165182
166183 // err modifies an error to contain the path of this filesystem.
1919 package filesystem
2020
2121 import (
22 "io/ioutil"
2223 "os"
2324 "path/filepath"
2425 "testing"
108109 }
109110 }
110111
112 // Test that when MOUNTPOINT/.fscrypt is a pre-created symlink, fscrypt will
113 // create/delete the metadata at the location pointed to by the symlink.
114 //
115 // This is a helper function that is called twice: once to test an absolute
116 // symlink and once to test a relative symlink.
117 func testSetupWithSymlink(t *testing.T, mnt *Mount, symlinkTarget string, realDir string) {
118 rawBaseDir := filepath.Join(mnt.Path, baseDirName)
119 if err := os.Symlink(symlinkTarget, rawBaseDir); err != nil {
120 t.Fatal(err)
121 }
122 defer os.Remove(rawBaseDir)
123
124 if err := mnt.Setup(); err != nil {
125 t.Fatal(err)
126 }
127 defer mnt.RemoveAllMetadata()
128 if err := mnt.CheckSetup(); err != nil {
129 t.Fatal(err)
130 }
131 if !isSymlink(rawBaseDir) {
132 t.Fatal("base dir should still be a symlink")
133 }
134 if !isDir(realDir) {
135 t.Fatal("real base dir should exist")
136 }
137 if err := mnt.RemoveAllMetadata(); err != nil {
138 t.Fatal(err)
139 }
140 if !isSymlink(rawBaseDir) {
141 t.Fatal("base dir should still be a symlink")
142 }
143 if isDir(realDir) {
144 t.Fatal("real base dir should no longer exist")
145 }
146 }
147
148 func TestSetupWithAbsoluteSymlink(t *testing.T) {
149 mnt, err := getTestMount(t)
150 if err != nil {
151 t.Fatal(err)
152 }
153 tempDir, err := ioutil.TempDir("", "fscrypt")
154 if err != nil {
155 t.Fatal(err)
156 }
157 defer os.RemoveAll(tempDir)
158 realDir := filepath.Join(tempDir, "realDir")
159 if realDir, err = filepath.Abs(realDir); err != nil {
160 t.Fatal(err)
161 }
162 testSetupWithSymlink(t, mnt, realDir, realDir)
163 }
164
165 func TestSetupWithRelativeSymlink(t *testing.T) {
166 mnt, err := getTestMount(t)
167 if err != nil {
168 t.Fatal(err)
169 }
170 realDir := filepath.Join(mnt.Path, ".fscrypt-real")
171 testSetupWithSymlink(t, mnt, ".fscrypt-real", realDir)
172 }
173
111174 // Adding a good Protector should succeed, adding a bad one should fail
112175 func TestAddProtector(t *testing.T) {
113176 mnt, err := getSetupMount(t)
5555 return info, err
5656 }
5757
58 // loggedLstat runs os.Lstat (doesn't dereference trailing symlink), but it logs
59 // the error if lstat returns any error other than nil or IsNotExist.
60 func loggedLstat(name string) (os.FileInfo, error) {
61 info, err := os.Lstat(name)
62 if err != nil && !os.IsNotExist(err) {
63 log.Print(err)
64 }
65 return info, err
66 }
67
5868 // isDir returns true if the path exists and is that of a directory.
5969 func isDir(path string) bool {
6070 info, err := loggedStat(path)
6575 func isDevice(path string) bool {
6676 info, err := loggedStat(path)
6777 return err == nil && info.Mode()&os.ModeDevice != 0
78 }
79
80 // isSymlink returns true if the path exists and is that of a symlink.
81 func isSymlink(path string) bool {
82 info, err := loggedLstat(path)
83 return err == nil && info.Mode()&os.ModeSymlink != 0
6884 }
6985
7086 // isDirCheckPerm returns true if the path exists and is a directory. If the