cmd/{snap-seccomp,snap-confine-ns},osutil,interfaces/account_control: workaround unit test failures on Arch #4135

Closed
wants to merge 20 commits into
from

Conversation

Projects
None yet
6 participants
Contributor

bboozzoo commented Nov 2, 2017

There is no 'shadow' group in ArchLinux, what causes a failure in cmd/snap-seccomp.TestCompile. Try to workaround the problem by looking at owning gid of /etc/shadow and figuring out the corresponding group name based on this.

As a side effect, a helper for looking up group name by gid was added. The implementation is based on similar code in https://tip.golang.org/src/os/user/cgo_lookup_unix.go

@zyga can you take a look?

Thank you for attacking this problem.

Some low and high level comments inline.

cmd/snap-seccomp/main_test.go
+ // /etc/shadow and use the gid that owns it
+ var shadowGid uint64
+ if gid, err := osutil.FindGid("shadow"); err != nil {
+ var stat syscall.Stat_t
@zyga

zyga Nov 2, 2017

Contributor

Why not just os.Stat?

@bboozzoo

bboozzoo Nov 2, 2017

Contributor

os.Stat does not carry any group information I'm afraid.

cmd/snap-seccomp/main_test.go
+ var shadowGid uint64
+ if gid, err := osutil.FindGid("shadow"); err != nil {
+ var stat syscall.Stat_t
+ err := syscall.Stat("/etc/shadow", &stat)
@zyga

zyga Nov 2, 2017

Contributor

I think we could twist this test a little by just stating /etc/shadow unconditionally and using whatever the group there for the rest of the code below. Alternatively use something that works on Arch as well and just carry on.

The real problem is in the account_control interface which grants fchown to u:root and g:shadow. This will fail at runtime and needs to be fixed to grant the actual group owner of /etc/shadow

@bboozzoo

bboozzoo Nov 2, 2017

Contributor

Agreed, skipping the fallback and doing stat() unconditionally will make the code easier. I'll amend the PR.

codecov-io commented Nov 2, 2017

Codecov Report

Merging #4135 into master will increase coverage by 0.14%.
The diff coverage is 75.18%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4135      +/-   ##
==========================================
+ Coverage   75.52%   75.67%   +0.14%     
==========================================
  Files         436      437       +1     
  Lines       37811    37870      +59     
==========================================
+ Hits        28558    28657      +99     
+ Misses       7256     7209      -47     
- Partials     1997     2004       +7
Impacted Files Coverage Δ
osutil/group.go 77.77% <100%> (+77.77%) ⬆️
osutil/cgo_group.go 69.66% <69.66%> (ø)
interfaces/builtin/account_control.go 80% <78.57%> (-20%) ⬇️
overlord/ifacestate/helpers.go 60.26% <0%> (+0.66%) ⬆️
cmd/snap/cmd_aliases.go 95% <0%> (+1.66%) ⬆️

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 47ceab8...0c6deba. Read the comment docs.

bboozzoo added some commits Nov 2, 2017

osutil: add helpers for looking up group name using gid
Add a helper for finding group using its gid. Implementation is based on
https://golang.org/src/os/user/cgo_lookup_unix.go.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
cmd/snap-seccomp: workaround issues with missing 'shadow' group
TestCompile sets up seccomp rules using 'shadow' group gid. In some distros,
namely ArchLinux, 'shadow' may not exist at all. In such case try to guess the
correct group by looking at owning gid of /etc/shadow and figure out group name
based on this.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
osutil: add helper for obtaining group information (ID and name) for …
…given file path

This is usefule when trying to determine group/gid of special system files, such
as /etc/shadow. Instead of breaking this down into separate calls, obtain the
information in one shot.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
cmd/snap-seccomp: update tests to use osutil.FindGroupOwning() helper
Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
interfaces/account_control: use /etc/shadow to obtain group informati…
…on for seccomp rules

Some distributions may not use the 'shadow' group. In such case, seccomp rules
will be incorrect, thus account-control interface may not work as expected.
Instead of assuming a particular group, obtain this information by directly
finding the owning group of /etc/shadow.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
cmd/snap-seccomp: fix uid/gid restrictions tests on Arch
'daemon' user UID is not guaranteed to be 1. Instead of hardcoding the uid,
determine it at runtime.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
cmd/snap-update-ns: do not assume that nogroup exists
Some distros may not have a 'nogroup' defined (eg. ArchLinux has 'nobody' group
instead). Try all known alternatives instead of using a hardcoded group name.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Contributor

bboozzoo commented Nov 3, 2017

Added one last patch to workaround missing 'nogroup' and rebased on top of c6d0682

@bboozzoo bboozzoo changed the title from Workaround unit test failures on Arch to cmd/{snap-seccomp,snap-confine-ns},osutil,interfaces/account_control: workaround unit test failures on Arch Nov 3, 2017

Looks good; some nits

osutil/group.go
@@ -63,6 +63,34 @@ func lookupGroup(groupname string) (string, error) {
return strconv.Itoa(int(grp.gr_gid)), nil
}
+// Use implementation from:
+// https://golang.org/src/os/user/cgo_lookup_unix.go
@chipaca

chipaca Nov 3, 2017

Member

could we take this one step further and pull in the Group type?

@bboozzoo

bboozzoo Nov 3, 2017

Contributor

you mean this one?

// from https://golang.org/src/os/user/user.go
type Group struct {
	Gid  string // group ID
	Name string // group name
}
@bboozzoo

bboozzoo Nov 3, 2017

Contributor

I pulled in os/user.Group as suggested. FindGroupOwning() was updated to return *Group, error, other calls were returning either gid or name, so I left those intact.

osutil/group.go
+ buf := alloc(groupBuffer)
+ defer buf.free()
+ err := retryWithBuffer(buf, func() syscall.Errno {
+ // mygetgrgid_r is a wrapper around getgrgid_r to
@chipaca

chipaca Nov 3, 2017

Member

I suspect this comment does not apply

@bboozzoo

bboozzoo Nov 3, 2017

Contributor

yup, thanks for spotting this

osutil/group.go
+
+ })
+ if err != nil {
+ return "", fmt.Errorf("group: lookup groupid %d: %v", gid, err)
@chipaca

chipaca Nov 3, 2017

Member

"cannot lookup group %d: %v" or somesuch (key being the starting with "cannot")

osutil/group.go
+ return "", fmt.Errorf("group: lookup groupid %d: %v", gid, err)
+ }
+ if result == nil {
+ return "", fmt.Errorf("group: unknown group %d", gid)
@chipaca

chipaca Nov 3, 2017

Member

errors should usually start with a cannot <do this thing you asked me to do>

@bboozzoo

bboozzoo Nov 3, 2017

Contributor

updated both messages

osutil/group.go
@@ -156,3 +184,28 @@ func FindGid(group string) (uint64, error) {
//return strconv.ParseUint(group.Gid, 10, 64)
return strconv.ParseUint(group, 10, 64)
}
+
+// FindGid returns the identifier of the given UNIX group name.
@chipaca

chipaca Nov 3, 2017

Member

FindGroup

@bboozzoo

bboozzoo Nov 3, 2017

Contributor

fixed

Thanks for working on this. Two suggestions for missing tests otherwise very nice.

cmd/snap-seccomp/main_test.go
- shadowGid, err := osutil.FindGid("shadow")
+ // The 'shadow' group is different in different distributions, instead
+ // of second guessing group name, look at the owner of /etc/shadow
+ shadowGid, shadowGroup, err := osutil.FindGroupOwning("/etc/shadow")
@mvo5

mvo5 Nov 3, 2017

Collaborator

Nice!

osutil/group.go
@@ -156,3 +184,28 @@ func FindGid(group string) (uint64, error) {
//return strconv.ParseUint(group.Gid, 10, 64)
return strconv.ParseUint(group, 10, 64)
}
+
+// FindGid returns the identifier of the given UNIX group name.
+func FindGroup(gid uint64) (string, error) {
@mvo5

mvo5 Nov 3, 2017

Collaborator

Can we write tests for this? A simple smoke test based on the local user or something?

@zyga

zyga Nov 3, 2017

Contributor

Ha, the reason this started was that there's just nothing sane about groups across systems.

Still +1 on a test.

@bboozzoo

bboozzoo Nov 3, 2017

Contributor

Added some tests to cover find group ID/name and group owning a particular file.

osutil/group.go
+}
+
+// FindGroupOwning obtains UNIX group ID and name owning file `path`.
+func FindGroupOwning(path string) (uint64, string, error) {
@mvo5

mvo5 Nov 3, 2017

Collaborator

And this as well? Again, something simple is enough for me. E.g. create a file, ensure its the group of the user running the test and call this helper or something like this?

bboozzoo added some commits Nov 3, 2017

osutil, cmd/snap-seccomp, interfaces: add osutil.Group type
Introduce a new type osutil.Group that is basically a wrapper around UNIX group
information. The implementation if based on Go standard library os/user package.
Update all corresponding call sites.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
osutil: remove misleading comment
The comment was copied over when importing code from Go os/user package.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
osutil: add tests for group related APIs
Add tests for group query APIs

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
osutil, cmd/snap-seccomp: better error messages in group lookup
Improve group query error messages. Update call sites.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
cmd/snap-update-ns: fix TestXSnapdGid
Fix the lookup of 'nogroup' like group in TestXSnapdGid.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Contributor

bboozzoo commented Nov 5, 2017

tests/main/static fails, because snapd can no longer be built with CGO_ENABLED=0. This is perhaps something worth discussing on the forums/IRC. Disabling cgo only works as long as you don't need cgo. In our case, we actually need it because of calls to getgrgid_r. When building with cgo disabled, Go toolchain was so nice to silently skip building osutil/group.go without a single word of warning only to producing uninformative interfaces/builtin/account_control.go:84:16: undefined: osutil.FindGroupOwning.

I will update the test to use -ldflags '-extldflags "-static"', but it's only half of solution. We need to decide which binaries must be built statically and which do not. As I understand, snap-seccomp, snap-exec, snap-update-ns are candidates. Out of these 3, snap-seccomp is a problem on Arch, as libseccomp is only avaialble as *.so. Given this, maybe tests/main/static doesn't even make sense.

Thoughts?

Contributor

zyga commented Nov 6, 2017

@bboozzoo to expand on the which binaries need to be statically linked. This is a bit more subtle and complex. The Ubuntu 16.04 build is special as it is used to construct the core snap which is participating in re-execution in many places.

On Arch we don't really need to link anything statically until we are ready to use base snaps where the base snap transition in the execution environment will pose a problem. At that time we should be able to use snapd from core so again, we don't really strongly need that.

bboozzoo added some commits Nov 7, 2017

osutil: split group lookup into cgo and !cgo stubs
Querying UNIX groups relies of C library calls getgrnam_r and getgrgid_r.
The code using osutil group API will not build with CGO_ENABLED=0. Provide stubs
that implement low-level calls and return known errors in such case.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>

Loos good! Just minor nits.

If I make the same point twice below you'll know github is weird(er).

osutil/group_test.go
-
- c.Check(group.Gid, Equals, self.Gid)
+ if c.Check(err, IsNil) {
+ c.Check(group.Gid, Equals, self.Gid)
@chipaca

chipaca Nov 7, 2017

Member

this is usually written

c.Assert(err, IsNil)
c.Check(...)
osutil/group_test.go
+
+func (s *groupSuite) TestSelfOwnedFile(c *C) {
+ self, err := RealUser()
+ c.Check(err, IsNil)
@chipaca

chipaca Nov 7, 2017

Member

in general you'd use Assert on errors, unless the error doesn't affect the rest of the test; here I think it very much does

osutil/group_test.go
+ defer os.Remove(name)
+
+ group, err := FindGroupOwning(name)
+ if c.Check(err, IsNil) {
@chipaca

chipaca Nov 7, 2017

Member

and here, c.Assert(err, IsNil) and you can do away with the if

osutil: use gocheck.C.Assert() where needed
Replace Check() calls with Assert()s in places where further test execution
makes no sense or will crash.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Contributor

bboozzoo commented Nov 8, 2017

Do we need another round of reviews or is this good to be merged now?

Contributor

zyga commented Nov 8, 2017

I'll do second review shortly (just woke up)

mvo5 approved these changes Nov 8, 2017

This looks very nice, thanks for doing these fixes. I have some nitpick comments inside about conventions/style etc. Please check them out but I'm fine merging this and doing the style fixes in a followup.

cmd/snap-update-ns/entry_test.go
- e = &mount.Entry{Options: []string{"x-snapd.gid=nogroup"}}
+ var nogroup string
+ var nogroupGid uint64
+ // try to find a suitable 'nogroup' like group
@mvo5

mvo5 Nov 8, 2017

Collaborator

(nitpick) Maybe expand the comment a tiny bit? Something like: try to find a suitable 'nogroup' like group, e.g. arch uses "nobody"

interfaces/builtin/account_control.go
+func (iface *accountControlInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, Attrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error {
+ if snippet, err := makeAccountControlSecCompSnippet(); err != nil {
+ return err
+ } else {
@mvo5

mvo5 Nov 8, 2017

Collaborator

(nitpick) we don't need the } else { here our convention is like: foo, err := thing()\n if err != nil {return err}\nuse(thing). So something like the lines below will be more idiomatic for our code base:

        snippet, err := makeAccountControlSecCompSnippet()
        if err != nil {
		return err
	}
	spec.AddSnippet(snippet)
	return nil

is fine.

osutil/group_test.go
+ *
+ */
+
+package osutil
@mvo5

mvo5 Nov 8, 2017

Collaborator

(nitpick) we use a slightly different pattern when writing (most) unit tests. The package is called foo_test instead of foo which forces to use only the public interfaces of the thing that gets tested. This is similar to what the go upstream source is using. If internal access is needed we use a export_test.go file that is part of package "foo" to only export things for tests. For this particular test changes should be minimal as it is only testing public functions. So just renaming the package from osutil to osutil_test should be enough (plus import of osutil and prefixing the methods).

osutil/group_test.go
+ . "gopkg.in/check.v1"
+)
+
+type groupSuite struct {
@mvo5

mvo5 Nov 8, 2017

Collaborator

(nitpick^2) empty structs are mostly written type groupSuite struct {}, i.e. no extra newline. We don't always follow that rule but mostly :)

osutil/group_test.go
+type groupSuite struct {
+}
+
+var _ = Suite(&groupSuite{})
@mvo5

mvo5 Nov 8, 2017

Collaborator

Thanks for adding those tests!

bboozzoo added some commits Nov 8, 2017

osutil: move group tests to osutil_test package
Refactor osutil group API tests to use a separate osutil_test package like the
rest of the code.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
cmd/snap-update-ns: comment on 'nobody' vs 'nogroup' use in tests
Leave more meaningful comment on why we need to go through the extra hoops.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
interfaces/builtin/account_control: simplify code in SecCompConnected…
…Plug()

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
dirs: use alt root when checking classic confinement support without …
…symlink workaround

The test needs be isolated from local filesystem state.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Merge remote-tracking branch 'origin/master' into bboozzoo/arch-fixes…
…-merge-master

Conflicts:
	cmd/snap-update-ns/entry_test.go

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>

mvo5 approved these changes Nov 8, 2017

Contributor

bboozzoo commented Nov 8, 2017

Merge? (once the tests are green)

Member

chipaca commented Nov 8, 2017

once green, go for it

Contributor

bboozzoo commented Nov 8, 2017

Interesting, travis failed with:

2017-11-08 12:33:57 Failed tasks: 1
    - linode:ubuntu-core-16-64:tests/main/snap-auto-mount
2017-11-08 12:33:57 Failed task prepare: 1
    - linode:fedora-25-64:tests/main/classic-custom-device-reg
2017-11-08 12:33:57 Failed task restore: 1
    - linode:fedora-25-64:tests/main/classic-custom-device-reg

linode:fedora-25-64:tests/main/classic-custom-device-reg error is:

error: cannot pack "/home/gopath/src/github.com/snapcore/snapd/tests/lib/snaps/classic-gadget": exit status 1

linode:ubuntu-core-16-64:tests/main/snap-auto-mount error:

+ echo 'The auto-mount magic has given us the assertion'
The auto-mount magic has given us the assertion
+ MATCH 'name: test-store'
+ snap known account-key
error: pattern not found, got:

Please hold on a bit. Let me please have a look into it.

Contributor

bboozzoo commented Nov 8, 2017

Closing this for now. As discussed in the meetng, we'll try to kill most of cgo code, meaning we'll drop g:{{group}} filter form account control seccomp template followed by dropping of all unnecessary group query code.

@bboozzoo bboozzoo closed this Nov 8, 2017

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