blob: 280db2b67fb9bbaa61c152d431ec27ea0a080f26 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Robert Kroegerc6175582014-11-21 16:03:16 -08005package impl
6
7import (
gauthamtfd1e34e2015-03-05 15:30:52 -08008 "fmt"
Arup Mukherjee746444f2015-04-16 19:13:24 -07009 "io"
Robert Kroegerc6175582014-11-21 16:03:16 -080010 "os"
Arup Mukherjee746444f2015-04-16 19:13:24 -070011 "os/exec"
Robert Kroegerc6175582014-11-21 16:03:16 -080012 "os/user"
Arup Mukherjee746444f2015-04-16 19:13:24 -070013 "strconv"
Robert Kroegerc6175582014-11-21 16:03:16 -080014
Todd Wang54feabe2015-04-15 23:38:26 -070015 "v.io/v23/context"
Ankurd8646812015-03-12 10:48:41 -070016 "v.io/v23/security"
Jiri Simsa6ac95222015-02-23 16:11:49 -080017 "v.io/v23/verror"
Jiri Simsa337af232015-02-27 14:36:46 -080018 "v.io/x/lib/vlog"
Robert Kroegerc6175582014-11-21 16:03:16 -080019)
20
Arup Mukherjee746444f2015-04-16 19:13:24 -070021type suidHelperState struct {
22 dmUser string // user that the device manager is running as
23 helperPath string // path to the suidhelper binary
24}
Robert Kroegerc6175582014-11-21 16:03:16 -080025
Arup Mukherjee746444f2015-04-16 19:13:24 -070026var suidHelper *suidHelperState
Robert Kroegerc6175582014-11-21 16:03:16 -080027
Arup Mukherjee746444f2015-04-16 19:13:24 -070028func initSuidHelper(helperPath string) {
29 if suidHelper != nil || helperPath == "" {
30 return
31 }
32
Robert Kroegerc6175582014-11-21 16:03:16 -080033 u, err := user.Current()
34 if err != nil {
35 vlog.Panicf("devicemanager has no current user: %v", err)
36 }
Arup Mukherjee746444f2015-04-16 19:13:24 -070037 suidHelper = &suidHelperState{
38 dmUser: u.Username,
39 helperPath: helperPath,
40 }
41}
42
Arup Mukherjeec6dd50e2015-04-23 15:20:56 -070043func (s suidHelperState) getCurrentUser() string {
44 return s.dmUser
45}
46
Arup Mukherjee746444f2015-04-16 19:13:24 -070047// terminatePid sends a SIGKILL to the target pid
48func (s suidHelperState) terminatePid(pid int, stdout, stderr io.Writer) error {
49 if err := s.internalModalOp(stdout, stderr, "--kill", strconv.Itoa(pid)); err != nil {
50 return fmt.Errorf("devicemanager's invocation of suidhelper to kill pid %v failed: %v", pid, err)
51 }
52 return nil
53}
54
55// deleteFileTree deletes a file or directory
56func (s suidHelperState) deleteFileTree(dirOrFile string, stdout, stderr io.Writer) error {
57 if err := s.internalModalOp(stdout, stderr, "--rm", dirOrFile); err != nil {
58 return fmt.Errorf("devicemanager's invocation of suidhelper delete %v failed: %v", dirOrFile, err)
59 }
60 return nil
61}
62
Arup Mukherjeec6dd50e2015-04-23 15:20:56 -070063// chown files or directories
64func (s suidHelperState) chownTree(username string, dirOrFile string, stdout, stderr io.Writer) error {
65 args := []string{"--chown", "--username", username, dirOrFile}
66
67 if err := s.internalModalOp(stdout, stderr, args...); err != nil {
68 return fmt.Errorf("devicemanager's invocation of suidhelper chown %v failed: %v", dirOrFile, err)
69 }
70 return nil
71}
72
Arup Mukherjee746444f2015-04-16 19:13:24 -070073type suidAppCmdArgs struct {
74 // args to helper
75 targetUser, progname, workspace, logdir, binpath string
76 // fields in exec.Cmd
77 env []string
78 stdout, stderr io.Writer
79 dir string
80 // arguments passed to app
81 appArgs []string
82}
83
84// getAppCmd produces an exec.Cmd that can be used to start an app
85func (s suidHelperState) getAppCmd(a *suidAppCmdArgs) (*exec.Cmd, error) {
86 if a.targetUser == "" || a.progname == "" || a.binpath == "" || a.workspace == "" || a.logdir == "" {
87 return nil, fmt.Errorf("Invalid args passed to getAppCmd: %+v", a)
88 }
89
90 cmd := exec.Command(s.helperPath)
91
92 switch yes, err := s.suidhelperEnabled(a.targetUser); {
93 case err != nil:
94 return nil, err
95 case yes:
96 cmd.Args = append(cmd.Args, "--username", a.targetUser)
97 case !yes:
98 cmd.Args = append(cmd.Args, "--username", a.targetUser, "--dryrun")
99 }
100
101 cmd.Args = append(cmd.Args, "--progname", a.progname)
102 cmd.Args = append(cmd.Args, "--workspace", a.workspace)
103 cmd.Args = append(cmd.Args, "--logdir", a.logdir)
104
105 cmd.Args = append(cmd.Args, "--run", a.binpath)
106 cmd.Args = append(cmd.Args, "--")
107
108 cmd.Args = append(cmd.Args, a.appArgs...)
109
110 cmd.Env = a.env
111 cmd.Stdout = a.stdout
112 cmd.Stderr = a.stderr
113 cmd.Dir = a.dir
114
115 return cmd, nil
116}
117
118// internalModalOp is a convenience routine containing the common part of all
119// modal operations. Only other routines implementing specific suidhelper operations
120// (like terminatePid and deleteFileTree) should call this directly.
121func (s suidHelperState) internalModalOp(stdout, stderr io.Writer, arg ...string) error {
122 cmd := exec.Command(s.helperPath)
123 cmd.Args = append(cmd.Args, arg...)
124 if stderr != nil {
125 cmd.Stderr = stderr
126 }
127 if stdout != nil {
128 cmd.Stdout = stdout
129 }
130
131 if err := cmd.Run(); err != nil {
132 vlog.Errorf("failed calling helper with args (%v):%v", arg, err)
Arup Mukherjeec6dd50e2015-04-23 15:20:56 -0700133 return err
Arup Mukherjee746444f2015-04-16 19:13:24 -0700134 }
135 return nil
Robert Kroegerc6175582014-11-21 16:03:16 -0800136}
137
138// isSetuid is defined like this so we can override its
139// implementation for tests.
140var isSetuid = func(fileStat os.FileInfo) bool {
141 vlog.VI(2).Infof("running the original isSetuid")
142 return fileStat.Mode()&os.ModeSetuid == os.ModeSetuid
143}
144
gauthamtfd1e34e2015-03-05 15:30:52 -0800145// suidhelperEnabled determines if the suidhelper must exist and be
Arup Mukherjee746444f2015-04-16 19:13:24 -0700146// setuid to run an application as system user targetUser. If false, the
Robert Kroegerc6175582014-11-21 16:03:16 -0800147// setuidhelper must be invoked with the --dryrun flag to skip making
148// system calls that will fail or provide apps with a trivial
149// priviledge escalation.
Arup Mukherjee746444f2015-04-16 19:13:24 -0700150func (s suidHelperState) suidhelperEnabled(targetUser string) (bool, error) {
151 helperStat, err := os.Stat(s.helperPath)
Robert Kroegerc6175582014-11-21 16:03:16 -0800152 if err != nil {
Arup Mukherjee746444f2015-04-16 19:13:24 -0700153 return false, verror.New(ErrOperationFailed, nil, fmt.Sprintf("Stat(%v) failed: %v. helper is required.", s.helperPath, err))
Robert Kroegerc6175582014-11-21 16:03:16 -0800154 }
155 haveHelper := isSetuid(helperStat)
156
157 switch {
Arup Mukherjee746444f2015-04-16 19:13:24 -0700158 case haveHelper && s.dmUser != targetUser:
Robert Kroegerc6175582014-11-21 16:03:16 -0800159 return true, nil
Arup Mukherjee746444f2015-04-16 19:13:24 -0700160 case haveHelper && s.dmUser == targetUser:
161 return false, verror.New(verror.ErrNoAccess, nil, fmt.Sprintf("suidhelperEnabled failed: %q == %q", s.dmUser, targetUser))
Robert Kroegerc6175582014-11-21 16:03:16 -0800162 default:
163 return false, nil
164 }
165 // Keep the compiler happy.
166 return false, nil
167}
168
gauthamtfd1e34e2015-03-05 15:30:52 -0800169// usernameForPrincipal returns the system name that the
Robert Kroegerc6175582014-11-21 16:03:16 -0800170// devicemanager will use to invoke apps.
171// TODO(rjkroege): This code assumes a desktop target and will need
172// to be reconsidered for embedded contexts.
Todd Wang4264e4b2015-04-16 22:43:40 -0700173func (s suidHelperState) usernameForPrincipal(ctx *context.T, call security.Call, uat BlessingSystemAssociationStore) string {
174 identityNames, _ := security.RemoteBlessingNames(ctx, call)
Robert Kroegerc6175582014-11-21 16:03:16 -0800175 systemName, present := uat.SystemAccountForBlessings(identityNames)
Robert Kroegerc6175582014-11-21 16:03:16 -0800176 if present {
177 return systemName
178 } else {
Arup Mukherjee746444f2015-04-16 19:13:24 -0700179 return s.dmUser
Robert Kroegerc6175582014-11-21 16:03:16 -0800180 }
181}