Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
cmd/snap-update-ns: add secureMkfileAll #4169
08d3ed6
dcb1375
a260e40
c192276
5e89cf2
0aab1e7
e3c1d94
962acbc
2b98a9b
b853dac
0c67a1f
bee7986
cmd/snap-update-ns: add secureMkfileAll
This patch adds a function similar to secureMkdirAll that instead of creating a number of directories instead creates a number of directories and a final leaf file. The purpose of this function is to create empty files as bind mount targets for files present in a read-only location that needs to become writable. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
- Loading branch information...
| @@ -101,7 +101,7 @@ func secureMkDir(fd int, segments []string, i int, perm os.FileMode, uid, gid in | ||
| const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_DIRECTORY | ||
| - if err = sysMkdirat(fd, segment, uint32(perm)); err != nil { | ||
| + if err = sysMkdirat(fd, segment, uint32(perm.Perm())); err != nil { | ||
zyga
Contributor
|
||
| switch err { | ||
| case syscall.EEXIST: | ||
| made = false | ||
| @@ -129,6 +129,46 @@ func secureMkDir(fd int, segments []string, i int, perm os.FileMode, uid, gid in | ||
| } | ||
| // secureMkdirAll is the secure variant of os.MkdirAll. | ||
jdstrand
Contributor
|
||
| +func secureMkFile(fd int, segments []string, i int, perm os.FileMode, uid, gid int) error { | ||
| + logger.Debugf("secure-mk-file %d %q %d %v %d %d", fd, segments, i, perm, uid, gid) | ||
| + segment := segments[i] | ||
| + made := true | ||
| + var newFd int | ||
| + var err error | ||
| + | ||
| + const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_RDWR | ||
jdstrand
Contributor
|
||
| + | ||
| + // Open the final path segment as a file. Try to create the file (so that | ||
| + // we know if we need to chown it) but fall back to just opening an | ||
| + // existing one. | ||
| + | ||
| + newFd, err = sysOpenat(fd, segment, openFlags|syscall.O_CREAT|syscall.O_EXCL, uint32(perm.Perm())) | ||
| + if err != nil { | ||
| + switch err { | ||
| + case syscall.EEXIST: | ||
| + // If the file exists then just open it without O_EXCL | ||
|
|
||
| + newFd, err = sysOpenat(fd, segment, openFlags, 0) | ||
| + if err != nil { | ||
| + return fmt.Errorf("cannot open file %q: %v", segment, err) | ||
| + } | ||
| + made = false | ||
| + default: | ||
| + return fmt.Errorf("cannot open file %q: %v", segment, err) | ||
| + } | ||
| + } | ||
| + defer sysClose(newFd) | ||
| + | ||
| + if made { | ||
| + // Chown the file if we made it. | ||
| + if err := sysFchown(newFd, uid, gid); err != nil { | ||
| + return fmt.Errorf("cannot chown file %q to %d.%d: %v", segment, uid, gid, err) | ||
jdstrand
Contributor
|
||
| + } | ||
| + } | ||
| + | ||
| + return nil | ||
jdstrand
Contributor
|
||
| +} | ||
| + | ||
| +// SecureMkdirAll is the secure variant of os.MkdirAll. | ||
| // | ||
| // Unlike the regular version this implementation does not follow any symbolic | ||
| // links. At all times the new directory segment is created using mkdirat(2) | ||
| @@ -169,6 +209,38 @@ func secureMkdirAll(name string, perm os.FileMode, uid, gid int) error { | ||
| return nil | ||
| } | ||
| +// secureMkfileAll is a secure implementation of "mkdir -p $(dirname $1) && touch $1". | ||
| +// | ||
| +// This function is like secureMkdirAll but it creates an empty file instead of | ||
| +// a directory for the final path component. Each created directory component | ||
| +// is chowned to the desired user and group. | ||
| +func secureMkfileAll(name string, perm os.FileMode, uid, gid int) error { | ||
| + logger.Debugf("secure-mkfile-all %q %q %d %d", name, perm, uid, gid) | ||
| + | ||
bboozzoo
Contributor
|
||
| + // Only support absolute paths to avoid bugs in snap-confine when | ||
| + // called from anywhere. | ||
| + if !filepath.IsAbs(name) { | ||
| + return fmt.Errorf("cannot create file with relative path: %q", name) | ||
| + } | ||
| + if strings.HasSuffix(name, "/") { | ||
| + return fmt.Errorf("cannot create non-file path: %q", name) | ||
| + } | ||
jdstrand
Contributor
|
||
| + segments := strings.FieldsFunc(filepath.Clean(name), func(c rune) bool { return c == '/' }) | ||
| + | ||
| + // Create the prefix. | ||
| + fd, err := secureMkPrefix(segments, perm, uid, gid) | ||
| + if err != nil { | ||
| + return err | ||
| + } | ||
| + defer sysClose(fd) | ||
| + | ||
| + if len(segments) > 0 { | ||
| + // Create the final segment as a file. | ||
| + err = secureMkFile(fd, segments, len(segments)-1, perm, uid, gid) | ||
| + } | ||
| + return err | ||
| +} | ||
| + | ||
| func ensureMountPoint(path string, mode os.FileMode, uid int, gid int) error { | ||
| // If the mount point is not present then create a directory in its | ||
| // place. This is very naive, doesn't handle read-only file systems | ||
This has the equivalent result for sysMkdirat and is consistent with perm used elsewhere, but seems superfluous. Not a blocker but curious if you made this change for something other than consistency?