Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // 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 Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 5 | package impl |
| 6 | |
| 7 | import ( |
gauthamt | fd1e34e | 2015-03-05 15:30:52 -0800 | [diff] [blame] | 8 | "fmt" |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 9 | "io" |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 10 | "os" |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 11 | "os/exec" |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 12 | "os/user" |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 13 | "strconv" |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 14 | |
Todd Wang | 54feabe | 2015-04-15 23:38:26 -0700 | [diff] [blame] | 15 | "v.io/v23/context" |
Ankur | d864681 | 2015-03-12 10:48:41 -0700 | [diff] [blame] | 16 | "v.io/v23/security" |
Jiri Simsa | 6ac9522 | 2015-02-23 16:11:49 -0800 | [diff] [blame] | 17 | "v.io/v23/verror" |
Jiri Simsa | 337af23 | 2015-02-27 14:36:46 -0800 | [diff] [blame] | 18 | "v.io/x/lib/vlog" |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 19 | ) |
| 20 | |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 21 | type suidHelperState struct { |
| 22 | dmUser string // user that the device manager is running as |
| 23 | helperPath string // path to the suidhelper binary |
| 24 | } |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 25 | |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 26 | var suidHelper *suidHelperState |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 27 | |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 28 | func initSuidHelper(helperPath string) { |
| 29 | if suidHelper != nil || helperPath == "" { |
| 30 | return |
| 31 | } |
| 32 | |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 33 | u, err := user.Current() |
| 34 | if err != nil { |
| 35 | vlog.Panicf("devicemanager has no current user: %v", err) |
| 36 | } |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 37 | suidHelper = &suidHelperState{ |
| 38 | dmUser: u.Username, |
| 39 | helperPath: helperPath, |
| 40 | } |
| 41 | } |
| 42 | |
Arup Mukherjee | c6dd50e | 2015-04-23 15:20:56 -0700 | [diff] [blame] | 43 | func (s suidHelperState) getCurrentUser() string { |
| 44 | return s.dmUser |
| 45 | } |
| 46 | |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 47 | // terminatePid sends a SIGKILL to the target pid |
| 48 | func (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 |
| 56 | func (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 Mukherjee | c6dd50e | 2015-04-23 15:20:56 -0700 | [diff] [blame] | 63 | // chown files or directories |
| 64 | func (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 Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 73 | type 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 |
| 85 | func (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. |
| 121 | func (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 Mukherjee | c6dd50e | 2015-04-23 15:20:56 -0700 | [diff] [blame] | 133 | return err |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 134 | } |
| 135 | return nil |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | // isSetuid is defined like this so we can override its |
| 139 | // implementation for tests. |
| 140 | var 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 | |
gauthamt | fd1e34e | 2015-03-05 15:30:52 -0800 | [diff] [blame] | 145 | // suidhelperEnabled determines if the suidhelper must exist and be |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 146 | // setuid to run an application as system user targetUser. If false, the |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 147 | // 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 Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 150 | func (s suidHelperState) suidhelperEnabled(targetUser string) (bool, error) { |
| 151 | helperStat, err := os.Stat(s.helperPath) |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 152 | if err != nil { |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 153 | return false, verror.New(ErrOperationFailed, nil, fmt.Sprintf("Stat(%v) failed: %v. helper is required.", s.helperPath, err)) |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 154 | } |
| 155 | haveHelper := isSetuid(helperStat) |
| 156 | |
| 157 | switch { |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 158 | case haveHelper && s.dmUser != targetUser: |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 159 | return true, nil |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 160 | case haveHelper && s.dmUser == targetUser: |
| 161 | return false, verror.New(verror.ErrNoAccess, nil, fmt.Sprintf("suidhelperEnabled failed: %q == %q", s.dmUser, targetUser)) |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 162 | default: |
| 163 | return false, nil |
| 164 | } |
| 165 | // Keep the compiler happy. |
| 166 | return false, nil |
| 167 | } |
| 168 | |
gauthamt | fd1e34e | 2015-03-05 15:30:52 -0800 | [diff] [blame] | 169 | // usernameForPrincipal returns the system name that the |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 170 | // 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 Wang | 4264e4b | 2015-04-16 22:43:40 -0700 | [diff] [blame] | 173 | func (s suidHelperState) usernameForPrincipal(ctx *context.T, call security.Call, uat BlessingSystemAssociationStore) string { |
| 174 | identityNames, _ := security.RemoteBlessingNames(ctx, call) |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 175 | systemName, present := uat.SystemAccountForBlessings(identityNames) |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 176 | if present { |
| 177 | return systemName |
| 178 | } else { |
Arup Mukherjee | 746444f | 2015-04-16 19:13:24 -0700 | [diff] [blame] | 179 | return s.dmUser |
Robert Kroeger | c617558 | 2014-11-21 16:03:16 -0800 | [diff] [blame] | 180 | } |
| 181 | } |