cmd/snap-update-ns: add secureMkfileAll #4169

Merged
merged 12 commits into from Nov 16, 2017

Conversation

Projects
None yet
4 participants
Contributor

zyga commented Nov 7, 2017

This branch is based on #4163

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

codecov-io commented Nov 7, 2017

Codecov Report

Merging #4169 into master will increase coverage by 0.15%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4169      +/-   ##
==========================================
+ Coverage    75.8%   75.96%   +0.15%     
==========================================
  Files         439      440       +1     
  Lines       37995    38427     +432     
==========================================
+ Hits        28803    29190     +387     
- Misses       7190     7223      +33     
- Partials     2002     2014      +12
Impacted Files Coverage Δ
cmd/snap-update-ns/utils.go 100% <100%> (ø) ⬆️
interfaces/builtin/account_control.go 80.64% <0%> (-19.36%) ⬇️
wrappers/services.go 76.53% <0%> (-3.79%) ⬇️
cmd/snap/cmd_aliases.go 93.33% <0%> (-1.67%) ⬇️
cmd/snap-update-ns/bootstrap.go 85.36% <0%> (-1%) ⬇️
snap/info_snap_yaml.go 93.83% <0%> (-0.21%) ⬇️
interfaces/systemd/spec.go 50.98% <0%> (ø) ⬆️
interfaces/builtin/location_observe.go 48.48% <0%> (ø) ⬆️
interfaces/kmod/spec.go 85.41% <0%> (ø) ⬆️
interfaces/udev/spec.go 95.18% <0%> (ø) ⬆️
... and 49 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d6c6506...bee7986. Read the comment docs.

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

bboozzoo Nov 8, 2017

Contributor

This should probably bail out early if path is /foo/ with / at the end and as a side effect paths like "/" will be rejected too.

if strings.HasSuffix(name, "/") { 
    return errors.New("not a file path")
}
@zyga

zyga Nov 8, 2017

Contributor

Done just below.

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>

zyga added some commits Nov 8, 2017

cmd/snap-update-ns: fix typo "another"
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

Overall seems fine, but a few questions in line and one change re filepath.Clean.

@@ -110,7 +110,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 {
@jdstrand

jdstrand Nov 14, 2017

Contributor

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?

@zyga

zyga Nov 15, 2017

Contributor

No, I just noticed after opening the other PR and I somehow landed the change here. Technically we want the Perm part of the mode so this is just for correctness in case of abuse.

cmd/snap-update-ns/utils.go
@@ -144,6 +144,46 @@ func secureMkDir(fd int, segments []string, i int, perm os.FileMode, uid, gid in
}
// secureMkdirAll is the secure variant of os.MkdirAll.
@jdstrand

jdstrand Nov 14, 2017

Contributor

This comment is wrong for this function. I think having a bigger comment ala secureMkdir is useful here, particularly that you are opening an existing file if it exists.

@zyga

zyga Nov 15, 2017

Contributor

Wrong merge. Updated.

cmd/snap-update-ns/utils.go
+ var newFd int
+ var err error
+
+ const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_RDWR
@jdstrand

jdstrand Nov 14, 2017

Contributor

Not a blocker, but curious why it is O_RDWR and O_RDONLY isn't supported? I'm not saying that O_RDONLY should be supported, but secureMkfile() doesn't scream 'create a read/write file'-- should the API be renamed secureMkfileReadWrite?

@zyga

zyga Nov 15, 2017

Contributor

Interesting point. Well, we don't really care (as the file is closed internally) and we check for EEXIST if the file was already there. I think all files must be writable to be made.

@jdstrand

jdstrand Nov 16, 2017

Contributor

Again, not that I am saying you have to, but you can use O_CREAT | O_RDONLY just fine.

@zyga

zyga Nov 16, 2017

Contributor

Man I only finally understand what's the point. Updated as we don't have to write to it.

I somehow instinctively assumed that O_CREAT needs O_RDWR :-)

cmd/snap-update-ns/utils.go
+ if err != nil {
+ switch err {
+ case syscall.EEXIST:
+ // If the file exists then just open it without O_EXCL
@jdstrand

jdstrand Nov 14, 2017

Contributor

and O_CREAT

+ 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

jdstrand Nov 14, 2017

Contributor

In secureMkdir you sysClose(newFd) with a comment. Shouldn't you be doing the same here? If not, perhaps add a comment why this is ok?

@zyga

zyga Nov 15, 2017

Contributor

No, because we don't return the fd here. It's defer closed already.

All the unit tests (with mocked syscalls) detect leaking descriptors.

+ }
+ }
+
+ return nil
@jdstrand

jdstrand Nov 14, 2017

Contributor

Why aren't you returning newFd here like with the secureMkdir? This is an API inconsistency that at the very least should be documented up above.

@zyga

zyga Nov 15, 2017

Contributor

Because the point of the API (at least for what I need it for) is to ensure the directory / file exists and there is no expected follow up. We don't write to those files, just use them as mount points.

cmd/snap-update-ns/utils.go
+ }
+ if strings.HasSuffix(name, "/") {
+ return fmt.Errorf("cannot create non-file path: %q", name)
+ }
@jdstrand

jdstrand Nov 14, 2017

Contributor

We should be checking the suffix after filepath.Clean() to avoid "/foo/..". Does it make sense to at the top of this function (and secureMkdirAll):

if filepath.Clean(name) != name {
   return fmt.Errorf("...")
}
@zyga

zyga Nov 15, 2017

Contributor

I think filepath.Clean chops the trailing / but I'll double-check. EDIT: Done in 0aab1e7

@zyga

zyga Nov 15, 2017

Contributor

Also changed this code to reject unclean paths. I think it's good to merge now.

cmd/snap-update-ns/utils_test.go
+ c.Assert(s.sys.Calls(), HasLen, 0)
+}
+
+// Ensure that we can "create" the root directory.
@jdstrand

jdstrand Nov 14, 2017

Contributor

This comment doesn't seem to go with the test.

@zyga

zyga Nov 15, 2017

Contributor

I meant that ironically but I re-worded this now

zyga added some commits Nov 15, 2017

cmd/snap-update-ns: detect read only filesystems in SecureMkfileAll
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd/snap-update-ns: tweak comment
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd/snap-update-ns: add smoke test for behavior of filepath.Clean
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd/snap-update-ns: tweak comment
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd/snap-update-ns: fix typo
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd/snap-update-ns: document secureMkFile
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd/snap-update-ns: add splitIntoSegments
This patch adds a small helper that is used by SecureMk{File,Dir}All to
ensure there's consistent behavior on unclean paths.

Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

Thanks for the changes.

cmd/snap-update-ns/utils_test.go
+ s.sys.InsertFault(`openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_RDWR|O_CREAT|O_EXCL 0755`, syscall.EROFS)
+ err := update.SecureMkfileAll("/rofs/path", 0755, 123, 456)
+ c.Check(err, ErrorMatches, `cannot operate on read-only filesystem at /rofs`)
+ // c.Assert(err.(*update.ReadOnlyFsError).Path, Equals, "/rofs")
@jdstrand

jdstrand Nov 16, 2017

Contributor

Did you mean to leave this commented out?

@zyga

zyga Nov 16, 2017

Contributor

No, good catch. Re-enabled now.

zyga added some commits Nov 16, 2017

cmd/snap-update-ns: re-enable commented-out test
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd/snap-update-ns: switch to O_RDONLY
We just need to create a file, not write to it, so it's sufficient to
O_RDONLY the open call. Thanks to jdstrand for explanation!

Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

@zyga zyga merged commit 14acf44 into snapcore:master Nov 16, 2017

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details

@zyga zyga deleted the zyga:tweak/secure-mkfile-all branch Nov 16, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment