github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/nsfix/nsfix.go (about) 1 /* 2 Copyright 2018 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package nsfix 18 19 import ( 20 "encoding/json" 21 "errors" 22 "flag" 23 "fmt" 24 "os" 25 "os/exec" 26 "reflect" 27 "strings" 28 "syscall" 29 30 "github.com/golang/glog" 31 ) 32 33 // ReexecHandler is a function that can be passed to 34 // RegisterReexec to be executed my nsfix mechanism after 35 // self-reexec. arg can be safely casted to the type of arg 36 // passed to RegisterReexec plus one level of pointer 37 // inderection, i.e. if you pass somestruct{} to RegisterReexec 38 // you may cast arg safely to *somestruct. 39 type ReexecHandler func(arg interface{}) (interface{}, error) 40 41 type handlerEntry struct { 42 handler ReexecHandler 43 argType reflect.Type 44 } 45 46 var reexecMap = map[string]handlerEntry{} 47 48 type retStruct struct { 49 Success bool 50 Result json.RawMessage 51 Error string 52 } 53 54 // RegisterReexec registers the specified function as a reexec handler. 55 // arg specifies the argument type to pass. Note that if you pass somestruct{} 56 // as arg, the handler will receive *somestruct as its argument (i.e. a level 57 // of pointer indirection is added). 58 func RegisterReexec(name string, handler ReexecHandler, arg interface{}) { 59 reexecMap[name] = handlerEntry{handler, reflect.TypeOf(arg)} 60 } 61 62 func getGlogLevel() int { 63 // XXX: apparently there's no better way to get the log level 64 i := 0 65 for ; i < 100; i++ { 66 if !glog.V(glog.Level(i)) { 67 return i - 1 68 } 69 } 70 return i 71 } 72 73 func restoreGlogLevel() { 74 logLevelStr := os.Getenv("NSFIX_LOG_LEVEL") 75 if logLevelStr == "" { 76 logLevelStr = "1" 77 } 78 // configure glog (apparently no better way to do it ...) 79 flag.CommandLine.Parse([]string{"-v=" + logLevelStr, "-logtostderr=true"}) 80 } 81 82 func marshalResult(ret interface{}, retErr error) ([]byte, error) { 83 var r retStruct 84 if retErr != nil { 85 r.Error = retErr.Error() 86 } else { 87 resultBytes, err := json.Marshal(ret) 88 if err != nil { 89 return nil, fmt.Errorf("error marshalling the result: %v", err) 90 } 91 92 r.Success = true 93 r.Result = json.RawMessage(resultBytes) 94 } 95 retBytes, err := json.Marshal(r) 96 if err != nil { 97 return nil, fmt.Errorf("error marshalling retStruct: %v", err) 98 } 99 return retBytes, nil 100 } 101 102 func unmarshalResult(retBytes []byte, ret interface{}) error { 103 var r retStruct 104 if err := json.Unmarshal(retBytes, &r); err != nil { 105 return fmt.Errorf("error unmarshalling the result: %v", err) 106 } 107 if !r.Success { 108 return errors.New(r.Error) 109 } 110 if ret != nil { 111 if err := json.Unmarshal(r.Result, ret); err != nil { 112 return fmt.Errorf("error unmarshalling the result: %v", err) 113 } 114 } 115 return nil 116 } 117 118 // HandleReexec handles executing the code in another namespace. 119 // If reexcution is requested, the function calls os.Exit() after 120 // handling it. 121 func HandleReexec() { 122 if os.Getenv("NSFIX_NS_PID") == "" { 123 return 124 } 125 126 restoreGlogLevel() 127 handlerName := os.Getenv("NSFIX_HANDLER") 128 if handlerName == "" { 129 glog.Fatal("NSFIX_HANDLER not set") 130 } 131 entry, found := reexecMap[handlerName] 132 if !found { 133 glog.Fatalf("Bad NSFIX_HANDLER %q", handlerName) 134 } 135 136 var arg interface{} 137 if entry.argType != nil { 138 arg = reflect.New(entry.argType).Interface() 139 argStr := os.Getenv("NSFIX_ARG") 140 if argStr != "" { 141 if err := json.Unmarshal([]byte(argStr), arg); err != nil { 142 glog.Fatalf("Can't unmarshal NSFIX_ARG (NSFIX_HANDLER %q):\n%s\n", handlerName, argStr) 143 } 144 } 145 } 146 147 spawned := os.Getenv("NSFIX_SPAWN") != "" 148 switch ret, err := entry.handler(arg); { 149 case err != nil && !spawned: 150 glog.Fatalf("Error invoking NSFIX_HANDLER %q: %v", handlerName, err) 151 case err == nil && !spawned: 152 os.Exit(0) 153 default: 154 outBytes, err := marshalResult(ret, err) 155 if err != nil { 156 glog.Fatalf("Error marshalling the result from NSFIX_HANDLER %q: %v", handlerName, err) 157 } 158 os.Stdout.Write(outBytes) 159 os.Exit(0) 160 } 161 } 162 163 // Call describes a call to be executed in network, mount, UTS 164 // and IPC namespaces of another process. 165 type Call struct { 166 targetPid int 167 handlerName string 168 arg interface{} 169 remountSys bool 170 dropPrivs bool 171 } 172 173 // NewCall makes a new Call structure with specified 174 // handlerName using PID 1. 175 func NewCall(handlerName string) *Call { 176 return &Call{ 177 targetPid: 1, 178 handlerName: handlerName, 179 } 180 } 181 182 // TargetPid sets target PID value for Call 183 func (c *Call) TargetPid(targetPid int) *Call { 184 c.targetPid = targetPid 185 return c 186 } 187 188 // Arg sets argument for Call 189 func (c *Call) Arg(arg interface{}) *Call { 190 c.arg = arg 191 return c 192 } 193 194 // RemountSys instructs Call to remount /sys in the new process 195 func (c *Call) RemountSys() *Call { 196 c.remountSys = true 197 return c 198 } 199 200 // DropPrivs instructs Call to drop privileges in the new process 201 func (c *Call) DropPrivs() *Call { 202 c.dropPrivs = true 203 return c 204 } 205 206 func (c *Call) getEnvForExec(spawn bool) ([]string, error) { 207 env := os.Environ() 208 filteredEnv := []string{} 209 for _, envItem := range env { 210 if !strings.HasPrefix(envItem, "NSFIX_") { 211 filteredEnv = append(filteredEnv, envItem) 212 } 213 } 214 215 if c.arg != nil { 216 argBytes, err := json.Marshal(c.arg) 217 if err != nil { 218 return nil, fmt.Errorf("error marshalling handler arg: %v", err) 219 } 220 filteredEnv = append(filteredEnv, fmt.Sprintf("NSFIX_ARG=%s", argBytes)) 221 } 222 223 if c.remountSys { 224 filteredEnv = append(filteredEnv, "NSFIX_REMOUNT_SYS=1") 225 } 226 227 if c.dropPrivs { 228 filteredEnv = append(filteredEnv, "NSFIX_DROP_PRIVS=1") 229 } 230 231 if spawn { 232 filteredEnv = append(filteredEnv, "NSFIX_SPAWN=1") 233 } 234 235 return append(filteredEnv, 236 fmt.Sprintf("NSFIX_NS_PID=%d", c.targetPid), 237 fmt.Sprintf("NSFIX_HANDLER=%s", c.handlerName), 238 fmt.Sprintf("NSFIX_LOG_LEVEL=%d", getGlogLevel())), nil 239 } 240 241 // SwitchToNamespaces executes the specified handler using network, 242 // mount, UTS and IPC namespaces of the specified process. It passes 243 // the argument to the handler using JSON serialization. The current 244 // process gets replaced by the new one. If dropPrivs is true, the new 245 // process will execute using non-root uid/gid (using real uid/gid of 246 // the process if they're non-zero or 65534 which is nobody/nogroup) 247 func (c *Call) SwitchToNamespaces() error { 248 env, err := c.getEnvForExec(false) 249 if err != nil { 250 return err 251 } 252 return syscall.Exec(os.Args[0], os.Args[:1], env) 253 } 254 255 // SpawnInNamespaces executes the specified handler using network, 256 // mount, UTS and IPC namespaces of the specified process. It passes 257 // the argument to the handler using JSON serialization. It then 258 // returns the value returned by the handler (also via JSON 259 // serialization + deserialization). If dropPrivs is true, the new 260 // process will execute using non-root uid/gid (using real uid/gid of 261 // the process if they're non-zero or 65534 which is nobody/nogroup) 262 func (c *Call) SpawnInNamespaces(ret interface{}) error { 263 env, err := c.getEnvForExec(true) 264 if err != nil { 265 return err 266 } 267 268 cmd := exec.Command(os.Args[0]) 269 cmd.Env = env 270 cmd.Stderr = os.Stderr 271 out, err := cmd.Output() 272 if err != nil { 273 return fmt.Errorf("reexec caused error: %v", err) 274 } 275 276 return unmarshalResult(out, ret) 277 }