Skip to content

Commit df1f147

Browse files
authored
fix: Directories created via panel are owned by root:root (#183)
* fix: Directories created via panel are owned by root:root * make mkdirall hand back the dirs it created so we chown just those
1 parent 58c5717 commit df1f147

6 files changed

Lines changed: 119 additions & 37 deletions

File tree

internal/ufs/filesystem.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,14 @@ type Filesystem interface {
7474
Mkdir(name string, perm FileMode) error
7575

7676
// MkdirAll creates a directory named path, along with any necessary
77-
// parents, and returns nil, or else returns an error.
78-
//
79-
// The permission bits perm (before umask) are used for all
80-
// directories that MkdirAll creates.
81-
// If path is already a directory, MkdirAll does nothing
82-
// and returns nil.
83-
MkdirAll(path string, perm FileMode) error
77+
// parents, and returns the directories it created, or else returns an
78+
// error.
79+
//
80+
// The returned directories are ordered from shallowest to deepest. The
81+
// permission bits perm (before umask) are used for all directories that
82+
// MkdirAll creates. If path is already a directory, MkdirAll does nothing
83+
// and returns no created directories.
84+
MkdirAll(path string, perm FileMode) ([]string, error)
8485

8586
// Open opens the named file for reading.
8687
//

internal/ufs/fs_unix.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -207,17 +207,17 @@ func (fs *UnixFS) mkdirat(op string, dirfd int, name string, mode FileMode) erro
207207
}
208208

209209
// MkdirAll creates a directory named path, along with any necessary
210-
// parents, and returns nil, or else returns an error.
210+
// parents, and returns the directories it created, or else returns an error.
211211
//
212-
// The permission bits perm (before umask) are used for all
213-
// directories that MkdirAll creates.
214-
// If path is already a directory, MkdirAll does nothing
215-
// and returns nil.
216-
func (fs *UnixFS) MkdirAll(name string, mode FileMode) error {
212+
// The returned directories are ordered from shallowest to deepest. The
213+
// permission bits perm (before umask) are used for all directories that
214+
// MkdirAll creates. If path is already a directory, MkdirAll does nothing and
215+
// returns no created directories
216+
func (fs *UnixFS) MkdirAll(name string, mode FileMode) ([]string, error) {
217217
// Ensure name is somewhat clean before continuing.
218218
name, err := fs.unsafePath(name)
219219
if err != nil {
220-
return err
220+
return nil, err
221221
}
222222
return fs.mkdirAll(name, mode)
223223
}
@@ -471,7 +471,7 @@ func (fs *UnixFS) Rename(oldpath, newpath string) error {
471471
if !errors.As(err, &pathErr) {
472472
return err
473473
}
474-
if err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
474+
if _, err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
475475
return err
476476
}
477477
newdirfd, newname, closeFd2, err = fs.safePath(newpath)
@@ -623,7 +623,7 @@ func (fs *UnixFS) TouchPath(path string) (int, string, func(), error, bool) {
623623
if !errors.As(err, &pathErr) {
624624
return dirfd, name, closeFd, err, false
625625
}
626-
if err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
626+
if _, err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
627627
return dirfd, name, closeFd, err, false
628628
}
629629

internal/ufs/fs_unix_test.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func TestUnixFS(t *testing.T) {
164164
}
165165

166166
// Create multiple nested directories.
167-
if err := fs.MkdirAll("ima_directory/ima_directory/ima_directory/ima_directory", 0o755); err != nil {
167+
if _, err := fs.MkdirAll("ima_directory/ima_directory/ima_directory/ima_directory", 0o755); err != nil {
168168
t.Error(err)
169169
return
170170
}
@@ -174,7 +174,7 @@ func TestUnixFS(t *testing.T) {
174174
}
175175

176176
// Test creating a directory under a symlink with a pre-existing directory.
177-
if err := fs.MkdirAll("ima_bad_link/ima_directory/ima_bad_directory/ima_bad_directory", 0o755); err == nil {
177+
if _, err := fs.MkdirAll("ima_bad_link/ima_directory/ima_bad_directory/ima_bad_directory", 0o755); err == nil {
178178
t.Error("expected an error")
179179
return
180180
}
@@ -324,12 +324,58 @@ func TestUnixFS_MkdirAll(t *testing.T) {
324324
}
325325
defer fs.Cleanup()
326326

327-
if err := fs.MkdirAll("/a/bunch/of/directories", 0o755); err != nil {
328-
t.Error(err)
329-
return
330-
}
327+
t.Run("creates and reports every missing directory", func(t *testing.T) {
328+
created, err := fs.MkdirAll("/a/bunch/of/directories", 0o755)
329+
if err != nil {
330+
t.Fatal(err)
331+
}
332+
333+
want := []string{"a", "a/bunch", "a/bunch/of", "a/bunch/of/directories"}
334+
if !slices.Equal(created, want) {
335+
t.Errorf("created = %v, want %v", created, want)
336+
}
337+
// Sanity check that everything we reported actually exists on disk.
338+
for _, dir := range want {
339+
st, err := os.Lstat(filepath.Join(fs.Root, dir))
340+
if err != nil {
341+
t.Errorf("Lstat %q: %v", dir, err)
342+
continue
343+
}
344+
if !st.IsDir() {
345+
t.Errorf("%q is not a directory", dir)
346+
}
347+
}
348+
})
349+
350+
t.Run("only reports the directories it creates", func(t *testing.T) {
351+
if _, err := fs.MkdirAll("partial/exists", 0o755); err != nil {
352+
t.Fatalf("seeding directories: %v", err)
353+
}
354+
355+
created, err := fs.MkdirAll("partial/exists/and/more", 0o755)
356+
if err != nil {
357+
t.Fatal(err)
358+
}
359+
360+
want := []string{"partial/exists/and", "partial/exists/and/more"}
361+
if !slices.Equal(created, want) {
362+
t.Errorf("created = %v, want %v", created, want)
363+
}
364+
})
331365

332-
// TODO: stat sanity check
366+
t.Run("reports nothing when the directory already exists", func(t *testing.T) {
367+
if _, err := fs.MkdirAll("already/here", 0o755); err != nil {
368+
t.Fatalf("seeding directories: %v", err)
369+
}
370+
371+
created, err := fs.MkdirAll("already/here", 0o755)
372+
if err != nil {
373+
t.Fatal(err)
374+
}
375+
if len(created) != 0 {
376+
t.Errorf("created = %v, want no directories", created)
377+
}
378+
})
333379
}
334380

335381
func TestUnixFS_Open(t *testing.T) {

internal/ufs/mkdir_unix.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
package ufs
1212

1313
// mkdirAll is a recursive Mkdir implementation that properly handles symlinks.
14-
func (fs *UnixFS) mkdirAll(name string, mode FileMode) error {
14+
//
15+
// It returns the directories it created, ordered from shallowest to deepest, so
16+
// callers can act on exactly the paths that were new (for example to change
17+
// their ownership). Directories that already existed are not included.
18+
func (fs *UnixFS) mkdirAll(name string, mode FileMode) ([]string, error) {
1519
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
1620
dir, err := fs.Lstat(name)
1721
if err == nil {
@@ -20,13 +24,13 @@ func (fs *UnixFS) mkdirAll(name string, mode FileMode) error {
2024
// to check instead.
2125
dir, err = fs.Stat(name)
2226
if err != nil {
23-
return err
27+
return nil, err
2428
}
2529
}
2630
if dir.IsDir() {
27-
return nil
31+
return nil, nil
2832
}
29-
return &PathError{Op: "mkdir", Path: name, Err: ErrNotDirectory}
33+
return nil, &PathError{Op: "mkdir", Path: name, Err: ErrNotDirectory}
3034
}
3135

3236
// Slow path: make sure parent exists and then call Mkdir for path.
@@ -40,11 +44,12 @@ func (fs *UnixFS) mkdirAll(name string, mode FileMode) error {
4044
j--
4145
}
4246

47+
var created []string
4348
if j > 1 {
4449
// Create parent.
45-
err = fs.mkdirAll(name[:j-1], mode)
50+
created, err = fs.mkdirAll(name[:j-1], mode)
4651
if err != nil {
47-
return err
52+
return created, err
4853
}
4954
}
5055

@@ -55,9 +60,9 @@ func (fs *UnixFS) mkdirAll(name string, mode FileMode) error {
5560
// double-checking that directory doesn't exist.
5661
dir, err1 := fs.Lstat(name)
5762
if err1 == nil && dir.IsDir() {
58-
return nil
63+
return created, nil
5964
}
60-
return err
65+
return created, err
6166
}
62-
return nil
67+
return append(created, name), nil
6368
}

server/filesystem/compress.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,14 +375,19 @@ func (fs *Filesystem) extractStream(ctx context.Context, opts extractStreamOptio
375375

376376
// Decompress and extract archive
377377
return ex.Extract(ctx, opts.Reader, func(ctx context.Context, f archives.FileInfo) error {
378-
if f.IsDir() {
379-
return nil
380-
}
381378
p := filepath.Join(opts.Directory, f.NameInArchive)
382-
// If it is ignored, just don't do anything with the file and skip over it.
379+
// If it is ignored, just don't do anything with the entry and skip over it.
383380
if err := fs.IsIgnored(p); err != nil {
384381
return nil
385382
}
383+
// Create directories explicitly; an empty one has no file to create it
384+
// implicitly and would otherwise be dropped during extraction.
385+
if f.IsDir() {
386+
if err := fs.mkdirAll(p, 0o755); err != nil {
387+
return wrapError(err, opts.FileName)
388+
}
389+
return nil
390+
}
386391
r, err := f.Open()
387392
if err != nil {
388393
return err

server/filesystem/filesystem.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ func (fs *Filesystem) Write(p string, r io.Reader, newSize int64, mode ufs.FileM
160160
return err
161161
}
162162

163+
// Ensure the parent directories exist and are owned by the server user
164+
// before creating the file. Touch would create any missing parents
165+
// implicitly, but as the user Wings runs as; creating them here lets us
166+
// chown the ones we add.
167+
if err := fs.mkdirAll(filepath.Dir(p), 0o755); err != nil {
168+
return err
169+
}
170+
163171
// Touch the file and return the handle to it at this point. This will
164172
// create or truncate the file, and create any necessary parent directories
165173
// if they are missing.
@@ -185,14 +193,15 @@ func (fs *Filesystem) Write(p string, r io.Reader, newSize int64, mode ufs.FileM
185193
if err := fs.chownFile(p); err != nil {
186194
return err
187195
}
196+
188197
// Return any remaining error.
189198
return err
190199
}
191200

192201
// CreateDirectory creates a new directory (name) at a specified path (p) for
193202
// the server.
194203
func (fs *Filesystem) CreateDirectory(name string, p string) error {
195-
return fs.unixFS.MkdirAll(filepath.Join(p, name), 0o755)
204+
return fs.mkdirAll(filepath.Join(p, name), 0o755)
196205
}
197206

198207
func (fs *Filesystem) Rename(oldpath, newpath string) error {
@@ -213,6 +222,22 @@ func (fs *Filesystem) chownFile(name string) error {
213222
return fs.unixFS.Lchown(name, uid, gid)
214223
}
215224

225+
// mkdirAll creates the directory p along with any missing parents, chowning
226+
// every directory it creates to the server user so they are not left owned by
227+
// the user Wings runs as.
228+
func (fs *Filesystem) mkdirAll(p string, mode ufs.FileMode) error {
229+
created, err := fs.unixFS.MkdirAll(p, mode)
230+
if err != nil {
231+
return err
232+
}
233+
for _, dir := range created {
234+
if err := fs.chownFile(dir); err != nil {
235+
return err
236+
}
237+
}
238+
return nil
239+
}
240+
216241
// Chown recursively iterates over a file or directory and sets the permissions on all of the
217242
// underlying files. Iterate over all of the files and directories. If it is a file just
218243
// go ahead and perform the chown operation. Otherwise dig deeper into the directory until

0 commit comments

Comments
 (0)