cmd/snap-update-ns: re-factor secureMkdirAll into secureMk{Prefix,Dir} #4163

Merged
merged 5 commits into from Nov 13, 2017

Conversation

Projects
None yet
6 participants
Contributor

zyga commented Nov 7, 2017

The act of "securely creating a directory" can be decomposed into
"securely create the prefix" and "securely create the leaf". Naturally
the leaf can now vary and we can easily create leaf directories, files
and symlinks.

This patch sets the stage for that to happen, by splitting the main
function into a pair of functions. On the outside nothing changes but
tests needed a slight tweak to accommodate how life-cycle of file
descriptors looks like.

There are also several new tests to improve code coverage in various
edge cases.

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

cmd/snap-update-ns: re-factor secureMkdirAll into secureMk{Prefix,Dir}
The act of "securely creating a directory" can be decomposed into
"securely create the prefix" and "securely create the leaf". Naturally
the leaf can now vary and we can easily create leaf directories, files
and symlinks.

This patch sets the stage for that to happen, by splitting the main
function into a pair of functions. On the outside nothing changes but
tests needed a slight tweak to accommodate how life-cycle of file
descriptors looks like.

There are also several new tests to improve code coverage in various
edge cases.

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

codecov-io commented Nov 8, 2017

Codecov Report

Merging #4163 into master will increase coverage by 0.07%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4163      +/-   ##
==========================================
+ Coverage   75.52%   75.59%   +0.07%     
==========================================
  Files         436      436              
  Lines       37766    37853      +87     
==========================================
+ Hits        28521    28615      +94     
+ Misses       7250     7245       -5     
+ Partials     1995     1993       -2
Impacted Files Coverage Δ
cmd/snap-update-ns/utils.go 100% <100%> (+14.06%) ⬆️
overlord/snapstate/snapstate.go 80.44% <0%> (-0.04%) ⬇️
overlord/assertstate/assertstate.go 77.59% <0%> (ø) ⬆️
overlord/snapstate/snapmgr.go 82.11% <0%> (+0.04%) ⬆️
overlord/snapstate/handlers.go 65.77% <0%> (+0.11%) ⬆️
corecfg/proxy.go 66.66% <0%> (+8.04%) ⬆️
corecfg/corecfg.go 44.44% <0%> (+20.44%) ⬆️
interfaces/kmod/kmod.go 72.72% <0%> (+27.27%) ⬆️

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 533f5fa...2d6647d. Read the comment docs.

Looks reasonable. There's this one thing with name = "/" that I indicated, might be worth double checking.

cmd/snap-update-ns/utils.go
+
+// secureMkdir creates a directory at i-th entry of absolute path represented
+// by segments. This function can be used to construct subsequent elements of
+// the constructed path.
@bboozzoo

bboozzoo Nov 8, 2017

Contributor

Maybe add a note that secureMkDir returns a file descriptor to newly created segment and the it's -1 on errors.

- // Open the root directory and start there.
- fd, err = sysOpen("/", openFlags, 0)
+ segments := strings.FieldsFunc(filepath.Clean(name), func(c rune) bool { return c == '/' })
+
@bboozzoo

bboozzoo Nov 8, 2017

Contributor

You should probably check if len(segments) > 0. Haven't tracked through the code but I think you'll get a panic with name = "/".

zyga added some commits Nov 8, 2017

cmd/snap-update-ns: don't crash creating "/"
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd/snap-update-ns: add non-mocked unit test for secure-mkdir-all
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
cmd/snap-update-ns: expand comment
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

LGTM

cmd/snap-update-ns: fix typo "another"
Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
+}
+
+// Ensure that we can create a directory in the top-level directory.
+func (s *utilsSuite) TestSecureMkdirAllLevel1(c *C) {
@pedronis

pedronis Nov 13, 2017

Contributor

don't we need a test for for update.SecureMkDirAll("/") ? should that fail if perm or gid/uid aren't the only possible ones?

@zyga

zyga Nov 13, 2017

Contributor

I don't think this needs to be this paranoid. First of all, we don't chown things we don't create and we don't create / ever :-) (we just open it, there's no code to try to create it). Lastly we are still confined and we cannot open / in writable mode.

@pedronis

pedronis Nov 13, 2017

Contributor

either way we still need to test what happens

@zyga

zyga Nov 13, 2017

Contributor

+1 with two tiny nitpicks from me, and assuming Maciej's and Samuele's feedback is adressed.

+ // Declare var and don't assign-declare below to ensure we don't swallow
+ // any errors by mistake.
+ var err error
+ var fd int
@stolowski

stolowski Nov 13, 2017

Contributor

I'd init fd to -1 to be on the safe side.

@zyga

zyga Nov 13, 2017

Contributor

Commented in the other PR that we cannot do that as it triggers ineffectual assignment error.

+ segment := segments[i]
+ made := true
+ var err error
+ var newFd int
@stolowski

stolowski Nov 13, 2017

Contributor

I'd init newFd to -1 to be on the safe side.

@stolowski stolowski merged commit f6f387f into snapcore:master Nov 13, 2017

3 of 7 checks passed

xenial-i386 autopkgtest finished (failure)
Details
xenial-ppc64el autopkgtest finished (failure)
Details
xenial-amd64 autopkgtest running
Details
zesty-amd64 autopkgtest running
Details
artful-amd64 autopkgtest finished (success)
Details
artful-i386 autopkgtest finished (success)
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

The directory creation itself still looks ok (ie, going from '/' to '/path/to/something' via mkdirat/openat.

However I found the decomposition and resulting API a bit awkward. Can you describe why it needs to be this way?

@@ -45,6 +47,87 @@ var (
sysFchown = syscall.Fchown
)
+// Create directories for all but the last segments and return the file
@jdstrand

jdstrand Nov 13, 2017

Contributor

s/segments/segment/

@@ -45,6 +47,87 @@ var (
sysFchown = syscall.Fchown
)
+// Create directories for all but the last segments and return the file
+// descriptor to the leaf directory. This function is a base for secure
@jdstrand

jdstrand Nov 13, 2017

Contributor

Rather than 'leaf directory' I think you mean 'parent'. Honestly, I found the naming here a bit confusing. secureMkDirParent() might be more clear...

I still find the code hard to read and the decomposition feels awkward where secureMkDirPrefix is passed all the segments (elements or tokens might be a better name for the variable...) and it creates everything but the last one. Why not just pass secureMkDirAll the parent and be done with it?

Put another way, why 3 functions: secureMkDirAll which creates everything, secureMkDirPrefix which creates all but the last and secureMkDir which creates an arbitrary internal element? Why isn't it sufficient to have a single function that just goes from '/' to the path you want? I guess I don't understand the point of this PR-- can you describe where you are going and why this needs to be decomposed in this way?

@pedronis

pedronis Nov 13, 2017

Contributor

afaict the description says that the plan is to reuse and compose *Prefix together with functions that create symlinks or files instead of dirs

@jdstrand

jdstrand Nov 13, 2017

Contributor

That doesn't explain why the API is decomposed and implemented in the manner it is-- maybe its just me, but, while it works, I found it somewhat convoluted.

@jdstrand

jdstrand Nov 13, 2017

Contributor

For example, having one function to create a directory, one to create a symlink and one to create a file would be clear. The symlink and the file functions would simply call the directory function with the dirname of the symlink/file to make sure the directory was created before doing the symlink or the file operation.

+ "/"+strings.Join(segments[:i], "/"), err)
+ }
+ if made {
+ // Chown each segment that we made.
@jdstrand

jdstrand Nov 13, 2017

Contributor

This comment is misleading. You only created one segment. s/each/the/

- fd, err = sysOpenat(fd, segment, openFlags, 0)
+ if len(segments) > 0 {
+ // Create the final segment as a directory.
+ fd, err = secureMkDir(fd, segments, len(segments)-1, perm, uid, gid)
@jdstrand

jdstrand Nov 13, 2017

Contributor

Again, this is awkward without knowing the reason why you broke it up in this way. We do:

segments := ...
...
fd, err := secureMkPrefix(segments, perm, uid, gid)
...
fd, err = secureMkDir(fd, segments, len(segments)-1, perm, uid, gid)

To me, an API something along the lines of this seems more clear:

// create the dir and any needed parent directories
createParents := true
fd, err := secureMkDir(segments, perm, uid, gid, createParents)
...
// create only the specified directory and no parents
createParents = false
fd, err := secureMkDir(segments, perm, uid, gid, createParents)

Then if you just want to create the parent directory (and its parents) for some reason, you just do:

fd, err := secureMkDir(segments[0:len(segments)-1], perm, uid, gid, true)
Contributor

jdstrand commented Nov 13, 2017

Unfortunately this was merged without my review (I said I would complete it by today but by the time I got to it, it was merged).

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