bastion 0.55.3
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// opNprocTestUID is a uid no account on a test machine uses.
|
||||
const opNprocTestUID = 61999
|
||||
|
||||
// A user-privilege op starts however many tasks other programs already run
|
||||
// as its user (a busy machine's daemons running as nobody), and the cap on
|
||||
// its tasks still stops the op's own fork bomb.
|
||||
func TestAUserOpStartsWhateverElseRunsAsItsUser(t *testing.T) {
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("needs root")
|
||||
}
|
||||
if ok, why := opSandboxAvailable(); !ok {
|
||||
t.Skip(why)
|
||||
}
|
||||
opUnprivilegedIdentityHook = func() (uint32, uint32, error) { return opNprocTestUID, opNprocTestUID, nil }
|
||||
t.Cleanup(func() { opUnprivilegedIdentityHook = nil })
|
||||
var others []*exec.Cmd
|
||||
t.Cleanup(func() {
|
||||
for _, c := range others {
|
||||
_ = c.Process.Kill()
|
||||
_ = c.Wait()
|
||||
}
|
||||
})
|
||||
for i := 0; i < 64; i++ { // the sandbox's cap
|
||||
c := exec.Command("sleep", "60")
|
||||
c.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: opNprocTestUID, Gid: opNprocTestUID, NoSetGroups: true}}
|
||||
if err := c.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
others = append(others, c)
|
||||
}
|
||||
|
||||
runOp := func(extra string) (*opReport, error) {
|
||||
n := time.Now().UnixNano()
|
||||
root := filepath.Join(opTestRoot, fmt.Sprintf("np%d", n))
|
||||
opDir := filepath.Join(opTestRoot, fmt.Sprintf("npop%d", n))
|
||||
for _, p := range []string{root, opDir} {
|
||||
p := p
|
||||
t.Cleanup(func() { _ = os.RemoveAll(p) })
|
||||
}
|
||||
_ = os.MkdirAll(root, 0o777)
|
||||
_ = os.Chmod(root, 0o777)
|
||||
_ = os.MkdirAll(opDir, 0o755)
|
||||
for _, f := range opFixture(root, extra) {
|
||||
raw, _ := base64.StdEncoding.DecodeString(f.B64)
|
||||
_ = os.WriteFile(filepath.Join(opDir, f.Path), raw, os.FileMode(f.Mode))
|
||||
}
|
||||
man := strings.Replace(fmt.Sprintf(opTestManifest, root), "privilege: root", "privilege: user", 1)
|
||||
m, err := parseOpManifest([]byte(man))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rep, err := opRunSandboxed(opDir, m, opModeDryRun, `{"name":"x","count":1,"kind":"a"}`)
|
||||
if rep != nil {
|
||||
t.Cleanup(rep.cleanup)
|
||||
}
|
||||
return rep, err
|
||||
}
|
||||
|
||||
rep, err := runOp(`echo "RAN-AS=$(id -u)"; true`)
|
||||
if err != nil {
|
||||
t.Fatalf("a user op with 64 other tasks of its user on the machine: %v", err)
|
||||
}
|
||||
if !strings.Contains(rep.Output, fmt.Sprintf("RAN-AS=%d", opNprocTestUID)) {
|
||||
t.Errorf("the op did not run as its user: %q", rep.Output)
|
||||
}
|
||||
|
||||
// The op's own tasks are still capped: a loop starting 100 is stopped,
|
||||
// and only after about as many as the cap, its own, not the others'.
|
||||
rep, err = runOp(`i=0; while [ $i -lt 100 ]; do echo "STARTING=$i"; sleep 5 & i=$((i+1)); done; echo DONE; true`)
|
||||
if err == nil || !strings.Contains(strings.ToLower(err.Error()), "fork") || strings.Contains(rep.Output, "DONE") {
|
||||
t.Fatalf("100 background tasks were not stopped by the cap: %v, %q", err, rep.Output)
|
||||
}
|
||||
last := ""
|
||||
for _, line := range strings.Split(rep.Output, "\n") {
|
||||
if strings.HasPrefix(line, "STARTING=") {
|
||||
last = strings.TrimPrefix(line, "STARTING=")
|
||||
}
|
||||
}
|
||||
if n, _ := strconv.Atoi(last); n < 50 {
|
||||
t.Errorf("the cap stopped the op after %s of its own tasks: it counts other tasks too", last)
|
||||
}
|
||||
t.Logf("the op's fork loop was stopped at task %s", last)
|
||||
}
|
||||
Reference in New Issue
Block a user