-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
capability: add separate ambient and bound API #176
Changes from all commits
0b071ef
bb713bc
8f1f88b
f092a6d
a902dc6
9463687
c1ade77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,7 +42,7 @@ func requirePCapSet(t testing.TB) { | |
} | ||
|
||
// testInChild runs fn as a separate process, and returns its output. | ||
// This is useful for tests which manipulate capabilties, allowing to | ||
// This is useful for tests which manipulate capabilities, allowing to | ||
// preserve those of the main test process. | ||
// | ||
// The fn is a function which must end with os.Exit. In case exit code | ||
|
@@ -150,6 +150,7 @@ func TestAmbientCapSet(t *testing.T) { | |
} | ||
|
||
func childAmbientCapSet() { | ||
runtime.LockOSThread() | ||
// We can't use t.Log etc. here, yet filename and line number is nice | ||
// to have. Set up and use the standard logger for this. | ||
log.SetFlags(log.Lshortfile) | ||
|
@@ -227,3 +228,148 @@ func TestApplyOtherProcess(t *testing.T) { | |
} | ||
} | ||
} | ||
|
||
func TestGetSetResetAmbient(t *testing.T) { | ||
if runtime.GOOS != "linux" { | ||
_, err := GetAmbient(Cap(0)) | ||
if err == nil { | ||
t.Error(runtime.GOOS, ": want error, got nil") | ||
} | ||
err = SetAmbient(false, Cap(0)) | ||
if err == nil { | ||
t.Error(runtime.GOOS, ": want error, got nil") | ||
} | ||
err = ResetAmbient() | ||
if err == nil { | ||
t.Error(runtime.GOOS, ": want error, got nil") | ||
} | ||
return | ||
} | ||
|
||
requirePCapSet(t) | ||
out := testInChild(t, childGetSetResetAmbient) | ||
t.Logf("output from child:\n%s", out) | ||
} | ||
|
||
func childGetSetResetAmbient() { | ||
runtime.LockOSThread() | ||
log.SetFlags(log.Lshortfile) | ||
|
||
pid, err := NewPid2(0) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
list := []Cap{CAP_KILL, CAP_CHOWN, CAP_SYS_CHROOT} | ||
pid.Set(CAPS, list...) | ||
if err = pid.Apply(CAPS); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Set ambient caps from list. | ||
if err = SetAmbient(true, list...); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Check if they were set as expected. | ||
for _, cap := range list { | ||
want := true | ||
got, err := GetAmbient(cap) | ||
if err != nil { | ||
log.Fatalf("GetAmbient(%s): want nil, got error %v", cap, err) | ||
} else if want != got { | ||
log.Fatalf("Get(AMBIENT, %s): want %v, got %v", cap, want, got) | ||
} | ||
} | ||
|
||
// Lower one ambient cap. | ||
const unsetIdx = 1 | ||
if err = SetAmbient(false, list[unsetIdx]); err != nil { | ||
log.Fatal(err) | ||
} | ||
// Check they are set as expected. | ||
for i, cap := range list { | ||
want := i != unsetIdx | ||
got, err := GetAmbient(cap) | ||
if err != nil { | ||
log.Fatalf("GetAmbient(%s): want nil, got error %v", cap, err) | ||
} else if want != got { | ||
log.Fatalf("Get(AMBIENT, %s): want %v, got %v", cap, want, got) | ||
} | ||
} | ||
|
||
// Lower all ambient caps. | ||
if err = ResetAmbient(); err != nil { | ||
log.Fatal(err) | ||
} | ||
for _, cap := range list { | ||
want := false | ||
got, err := GetAmbient(cap) | ||
if err != nil { | ||
log.Fatalf("GetAmbient(%s): want nil, got error %v", cap, err) | ||
} else if want != got { | ||
log.Fatalf("Get(AMBIENT, %s): want %v, got %v", cap, want, got) | ||
} | ||
} | ||
os.Exit(0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LOL, had to look twice, but then saw it's called from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it's somewhat complicated here since we modify capabilities and if we do it in a current process the following tests are toasted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm way outside my understanding, but isn't that what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are actually re-executing the test binary here (or, rather, in It never occurred to be we can test all this in a separate go thread (rather than a process). Let me give it a try :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I quickly tried that and it did't work. I'll add it to TODO and merge this as is for now, since it's just a test case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, the Practically, for users of this package, it means we need to change capabilities (in a locked OS thread) and then re-exec (which is what runc does). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I was looking at capabilities being a per thread attribute and scratching my beard 😂 sorry it didn't work out, but at least now you know! |
||
} | ||
|
||
func TestGetBound(t *testing.T) { | ||
if runtime.GOOS != "linux" { | ||
_, err := GetBound(Cap(0)) | ||
if err == nil { | ||
t.Error(runtime.GOOS, ": want error, got nil") | ||
} | ||
return | ||
} | ||
|
||
last, err := LastCap() | ||
if err != nil { | ||
t.Fatalf("LastCap: %v", err) | ||
} | ||
for i := Cap(0); i < Cap(63); i++ { | ||
wantErr := i > last | ||
set, err := GetBound(i) | ||
t.Logf("GetBound(%q): %v, %v", i, set, err) | ||
if wantErr && err == nil { | ||
t.Errorf("GetBound(%q): want err, got nil", i) | ||
} else if !wantErr && err != nil { | ||
t.Errorf("GetBound(%q): want nil, got error %v", i, err) | ||
} | ||
} | ||
} | ||
|
||
func TestDropBound(t *testing.T) { | ||
if runtime.GOOS != "linux" { | ||
err := DropBound(Cap(0)) | ||
if err == nil { | ||
t.Error(runtime.GOOS, ": want error, got nil") | ||
} | ||
return | ||
} | ||
|
||
requirePCapSet(t) | ||
out := testInChild(t, childDropBound) | ||
t.Logf("output from child:\n%s", out) | ||
} | ||
|
||
func childDropBound() { | ||
runtime.LockOSThread() | ||
log.SetFlags(log.Lshortfile) | ||
|
||
for i := Cap(2); i < Cap(4); i++ { | ||
err := DropBound(i) | ||
if err != nil { | ||
log.Fatalf("DropBound(%q): want nil, got error %v", i, err) | ||
} | ||
set, err := GetBound(i) | ||
if err != nil { | ||
log.Fatalf("GetBound(%q): want nil, got error %v", i, err) | ||
} | ||
if set { | ||
log.Fatalf("GetBound(%q): want false, got true", i) | ||
} | ||
} | ||
|
||
os.Exit(0) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminds me that I stumbled on this package which had a "consider integrating with moby/sys todo; https://github.com/moby/moby/blob/dc225798cbddebd47bfaa0fd8337d145c91fc6ba/internal/unix_noeintr/fs_unix.go#L3-L5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That thing in there is something entirely different -- it's a retry-on-EINTR, and here we have ignore-EINVAL.
As to autogenerating stuff, even github.com/golang/go doesn't do it -- they use helper functions here and there but it's all manual.