lib/suid: Add kill functionality.
Add --kill to suidhelper, to be used by the device manager to stop apps
owned by other users
Change-Id: I2e0e3dd2437db855f903dd2fe3b9dc233f2ab03b
diff --git a/services/device/device/doc.go b/services/device/device/doc.go
index dbf6677..81a7276 100644
--- a/services/device/device/doc.go
+++ b/services/device/device/doc.go
@@ -38,6 +38,8 @@
log to standard error as well as files
-dryrun=false
Elides root-requiring systemcalls.
+ -kill=false
+ Kill process ids given as command-line arguments.
-log_backtrace_at=:0
when logging hits line file:N, emit a stack trace
-log_dir=
diff --git a/services/device/internal/impl/device_installer.go b/services/device/internal/impl/device_installer.go
index 9c0a322..4d66a49 100644
--- a/services/device/internal/impl/device_installer.go
+++ b/services/device/internal/impl/device_installer.go
@@ -284,6 +284,12 @@
}
cmd := exec.Command(helperPath)
cmd.Args = append(cmd.Args, "--rm", root)
+ if stderr != nil {
+ cmd.Stderr = stderr
+ }
+ if stdout != nil {
+ cmd.Stdout = stdout
+ }
if err := cmd.Run(); err != nil {
return fmt.Errorf("devicemanager's invocation of suidhelper to remove(%v) failed: %v", root, err)
}
diff --git a/services/device/internal/suid/args.go b/services/device/internal/suid/args.go
index 6a9ab66..10ccfac 100644
--- a/services/device/internal/suid/args.go
+++ b/services/device/internal/suid/args.go
@@ -24,6 +24,8 @@
errInvalidUID = verror.Register(pkgPath+".errInvalidUID", verror.NoRetry, "{1:}{2:} user.Lookup() returned an invalid uid {3}{:_}")
errInvalidGID = verror.Register(pkgPath+".errInvalidGID", verror.NoRetry, "{1:}{2:} user.Lookup() returned an invalid gid {3}{:_}")
errUIDTooLow = verror.Register(pkgPath+".errUIDTooLow", verror.NoRetry, "{1:}{2:} suidhelper uid {3} is not permitted because it is less than {4}{:_}")
+ errAtoiFailed = verror.Register(pkgPath+".errAtoiFailed", verror.NoRetry, "{1:}{2:} strconv.Atoi({3}) failed{:_}")
+ errInvalidFlags = verror.Register(pkgPath+".errInvalidFlags", verror.NoRetry, "{1:}{2:} invalid flags ({3} are set){:_}")
)
type WorkParameters struct {
@@ -36,6 +38,8 @@
envv []string
dryrun bool
remove bool
+ kill bool
+ killPids []int
}
type ArgsSavedForTest struct {
@@ -50,7 +54,7 @@
var (
flagUsername, flagWorkspace, flagLogDir, flagRun, flagProgName *string
flagMinimumUid *int64
- flagRemove, flagDryrun *bool
+ flagRemove, flagKill, flagDryrun *bool
)
func init() {
@@ -66,6 +70,7 @@
flagProgName = fs.String("progname", "unnamed_app", "Visible name of the application, used in argv[0]")
flagMinimumUid = fs.Int64("minuid", uidThreshold, "UIDs cannot be less than this number.")
flagRemove = fs.Bool("rm", false, "Remove the file trees given as command-line arguments.")
+ flagKill = fs.Bool("kill", false, "Kill process ids given as command-line arguments.")
flagDryrun = fs.Bool("dryrun", false, "Elides root-requiring systemcalls.")
}
@@ -82,12 +87,47 @@
// ParseArguments populates the WorkParameter object from the provided args
// and env strings.
func (wp *WorkParameters) ProcessArguments(fs *flag.FlagSet, env []string) error {
+ // --rm and --kill are modal. Complain if any other flag is set along with one of those.
+ if *flagRemove || *flagKill {
+ // Count flags that are set. The device manager test always sets --minuid=1
+ // and --test.run=TestSuidHelper so when in a test, tolerate those
+ flagsToIgnore := map[string]string{}
+ if os.Getenv("V23_SUIDHELPER_TEST") != "" {
+ flagsToIgnore["minuid"] = "1"
+ flagsToIgnore["test.run"] = "TestSuidHelper"
+ }
+
+ counter := 0
+ fs.Visit(func(f *flag.Flag) {
+ if flagsToIgnore[f.Name] != f.Value.String() {
+ counter++
+ }
+ })
+
+ if counter > 1 {
+ return verror.New(errInvalidFlags, nil, counter, "--rm and --kill cannot be used with any other flag")
+ }
+ }
+
if *flagRemove {
wp.remove = true
wp.argv = fs.Args()
return nil
}
+ if *flagKill {
+ wp.kill = true
+ for _, p := range fs.Args() {
+ pid, err := strconv.Atoi(p)
+ if err != nil {
+ wp.killPids = nil
+ return verror.New(errAtoiFailed, nil, p, err)
+ }
+ wp.killPids = append(wp.killPids, pid)
+ }
+ return nil
+ }
+
username := *flagUsername
if username == "" {
return verror.New(errUserNameMissing, nil)
diff --git a/services/device/internal/suid/args_test.go b/services/device/internal/suid/args_test.go
index 5476a06..092c1ab 100644
--- a/services/device/internal/suid/args_test.go
+++ b/services/device/internal/suid/args_test.go
@@ -41,6 +41,8 @@
envv: []string{"A=B"},
dryrun: false,
remove: false,
+ kill: false,
+ killPids: nil,
},
},
@@ -59,8 +61,11 @@
envv: []string{"A=B"},
dryrun: false,
remove: false,
+ kill: false,
+ killPids: nil,
},
},
+
{
[]string{"setuidhelper", "--username", testUserName},
[]string{"A=B"},
@@ -82,13 +87,47 @@
envv: nil,
dryrun: false,
remove: true,
+ kill: false,
+ killPids: nil,
},
},
+
{
- []string{"setuidhelper", "--username", testUserName},
+ []string{"setuidhelper", "--kill", "235", "451"},
[]string{"A=B"},
- errUIDTooLow.ID,
- WorkParameters{},
+ "",
+ WorkParameters{
+ uid: 0,
+ gid: 0,
+ workspace: "",
+ logDir: "",
+ argv0: "",
+ argv: nil,
+ envv: nil,
+ dryrun: false,
+ remove: false,
+ kill: true,
+ killPids: []int{235, 451},
+ },
+ },
+
+ {
+ []string{"setuidhelper", "--kill", "235", "451oops"},
+ []string{"A=B"},
+ errAtoiFailed.ID,
+ WorkParameters{
+ uid: 0,
+ gid: 0,
+ workspace: "",
+ logDir: "",
+ argv0: "",
+ argv: nil,
+ envv: nil,
+ dryrun: false,
+ remove: false,
+ kill: true,
+ killPids: nil,
+ },
},
{
@@ -106,6 +145,8 @@
envv: []string{"A=B"},
dryrun: true,
remove: false,
+ kill: false,
+ killPids: nil,
},
},
}
diff --git a/services/device/internal/suid/run.go b/services/device/internal/suid/run.go
index bedc968..3814353 100644
--- a/services/device/internal/suid/run.go
+++ b/services/device/internal/suid/run.go
@@ -18,6 +18,10 @@
return work.Remove()
}
+ if work.kill {
+ return work.Kill()
+ }
+
if err := work.Chown(); err != nil {
return err
}
diff --git a/services/device/internal/suid/system.go b/services/device/internal/suid/system.go
index de37d54..6aad84b 100644
--- a/services/device/internal/suid/system.go
+++ b/services/device/internal/suid/system.go
@@ -21,6 +21,8 @@
errGetwdFailed = verror.Register(pkgPath+".errGetwdFailed", verror.NoRetry, "{1:}{2:} os.Getwd failed{:_}")
errStartProcessFailed = verror.Register(pkgPath+".errStartProcessFailed", verror.NoRetry, "{1:}{2:} syscall.StartProcess({3}) failed{:_}")
errRemoveAllFailed = verror.Register(pkgPath+".errRemoveAllFailed", verror.NoRetry, "{1:}{2:} os.RemoveAll({3}) failed{:_}")
+ errFindProcessFailed = verror.Register(pkgPath+".errFindProcessFailed", verror.NoRetry, "{1:}{2:} os.FindProcess({3}) failed{:_}")
+ errKillFailed = verror.Register(pkgPath+".errKillFailed", verror.NoRetry, "{1:}{2:} os.Process.Kill({3}) failed{:_}")
)
// Chown is only availabe on UNIX platforms so this file has a build
@@ -106,3 +108,17 @@
}
return nil
}
+
+func (hw *WorkParameters) Kill() error {
+ for _, pid := range hw.killPids {
+ proc, err := os.FindProcess(pid)
+ if err != nil {
+ return verror.New(errFindProcessFailed, nil, pid, err)
+ }
+
+ if err = proc.Kill(); err != nil {
+ return verror.New(errKillFailed, nil, pid, err)
+ }
+ }
+ return nil
+}