-
Notifications
You must be signed in to change notification settings - Fork 582
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
osutil/sys, client: add sys.RunAsUidGid, use it for auth.json #4983
Changes from all commits
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 |
---|---|---|
|
@@ -20,7 +20,9 @@ | |
package sys | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"runtime" | ||
"syscall" | ||
"unsafe" | ||
) | ||
|
@@ -107,3 +109,61 @@ func FcntlGetFl(fd int) (int, error) { | |
} | ||
return int(flags), nil | ||
} | ||
|
||
// UnrecoverableError is an error that flags that things have Gone Wrong, the | ||
// runtime is in a bad state, and you should really quit. The intention is that | ||
// if you're trying to recover from a panic and find that the value of the panic | ||
// is an UnrecoverableError, you should just exit ASAP. | ||
type UnrecoverableError struct { | ||
Call string | ||
Err error | ||
} | ||
|
||
func (e UnrecoverableError) Error() string { | ||
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. Please document this error. 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. Missing docs :) |
||
return fmt.Sprintf("%s: %v", e.Call, e.Err) | ||
} | ||
|
||
// RunAsUidGid starts a goroutine, pins it to the OS thread, sets euid and egid, | ||
// and runs the function; after the function returns, it restores euid and egid. | ||
// | ||
// If restoring the original euid and egid fails this function will panic with | ||
// an UnrecoverableError, and you should _not_ try to recover from it: the | ||
// runtime itself is going to be in trouble. | ||
func RunAsUidGid(uid UserID, gid GroupID, f func() error) error { | ||
ch := make(chan error, 1) | ||
go func() { | ||
// from the docs: | ||
// until the goroutine exits or calls UnlockOSThread, it will | ||
// always execute in this thread, and no other goroutine can. | ||
// that last bit means it's safe to setuid/setgid in here, as no | ||
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. Nitpick, 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 torn between fixing it now, or landing it now and fixing it later :-) |
||
// other code will run. | ||
runtime.LockOSThread() | ||
|
||
ruid := Getuid() | ||
rgid := Getgid() | ||
|
||
if _, _, errno := syscall.RawSyscall(_SYS_SETREGID, FlagID, uintptr(gid), 0); errno == 0 { | ||
if _, _, errno := syscall.RawSyscall(_SYS_SETREUID, FlagID, uintptr(uid), 0); errno == 0 { | ||
ch <- f() | ||
// try to restore euid | ||
if _, _, errno := syscall.RawSyscall(_SYS_SETREUID, FlagID, uintptr(ruid), 0); errno != 0 { | ||
// ¯\_(ツ)_/¯ | ||
panic(UnrecoverableError{Call: "setreuid", Err: errno}) | ||
} | ||
} else { | ||
ch <- fmt.Errorf("setreuid: %v", errno) | ||
} | ||
|
||
// try to restore egid | ||
if _, _, errno := syscall.RawSyscall(_SYS_SETREGID, FlagID, uintptr(rgid), 0); errno != 0 { | ||
// ¯\_(ツ)_/¯ | ||
panic(UnrecoverableError{Call: "setregid", Err: errno}) | ||
} | ||
} else { | ||
ch <- fmt.Errorf("setregid: %v", errno) | ||
} | ||
|
||
runtime.UnlockOSThread() | ||
}() | ||
return <-ch | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
summary: test chattr | ||
# ubuntu-core doesn't have go :-) | ||
systems: [-ubuntu-core-16-*] | ||
execute: | | ||
go build ./testit.go | ||
test "$(./testit)" = "before: 0/0, during: 12345/12345 (<nil>), after: 0/0; status: OK" | ||
test "$(sudo -u '#12345' -g '#12345' ./testit)" = "before: 12345/12345, during: 12345/12345 (<nil>), after: 12345/12345; status: OK" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"runtime" | ||
"sync" | ||
|
||
"github.com/snapcore/snapd/osutil/sys" | ||
) | ||
|
||
var wg sync.WaitGroup | ||
var mu sync.Mutex | ||
|
||
func check(uids []sys.UserID, n int) { | ||
// spin | ||
for i := 0; i < 1<<30; i++ { | ||
} | ||
|
||
mu.Lock() | ||
uids[n] = sys.Geteuid() | ||
mu.Unlock() | ||
|
||
wg.Done() | ||
} | ||
|
||
func main() { | ||
orig := sys.Geteuid() | ||
before := fmt.Sprintf("%d/%d", sys.Geteuid(), sys.Getegid()) | ||
var during string | ||
err := sys.RunAsUidGid(12345, 12345, func() error { | ||
during = fmt.Sprintf("%d/%d", sys.Geteuid(), sys.Getegid()) | ||
return nil | ||
}) | ||
after := fmt.Sprintf("%d/%d", sys.Geteuid(), sys.Getegid()) | ||
|
||
N := 2 * runtime.NumCPU() | ||
uids := make([]sys.UserID, N) | ||
// launch a lot of goroutines so we cover all threads with space to spare | ||
for i := 0; i < N; i++ { | ||
wg.Add(1) | ||
go check(uids, i) | ||
} | ||
wg.Wait() | ||
|
||
bad := 0 | ||
for _, uid := range uids { | ||
if uid != orig { | ||
bad++ | ||
} | ||
} | ||
status := "OK" | ||
if bad != 0 { | ||
status = fmt.Sprintf("%d BAD!", bad) | ||
} | ||
|
||
fmt.Printf("before: %s, during: %s (%v), after: %s; status: %s\n", before, during, err, after, status) | ||
} |
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.
Hmm, just return the error directly? :-)
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.
Ah, the diff formatting confused me, never mind :)