github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+incompatible/plugin/rpcplugin/sandbox/sandbox_linux.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package sandbox 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "syscall" 17 "unsafe" 18 19 "github.com/pkg/errors" 20 "golang.org/x/sys/unix" 21 22 "github.com/mattermost/mattermost-server/plugin/rpcplugin" 23 ) 24 25 func init() { 26 if len(os.Args) < 3 || os.Args[0] != "sandbox.runProcess" { 27 return 28 } 29 30 var config Configuration 31 if err := json.Unmarshal([]byte(os.Args[1]), &config); err != nil { 32 fmt.Println(err.Error()) 33 os.Exit(1) 34 } 35 if err := runProcess(&config, os.Args[2]); err != nil { 36 if eerr, ok := err.(*exec.ExitError); ok { 37 if status, ok := eerr.Sys().(syscall.WaitStatus); ok { 38 os.Exit(status.ExitStatus()) 39 } 40 } 41 fmt.Println(err.Error()) 42 os.Exit(1) 43 } 44 os.Exit(0) 45 } 46 47 func systemMountPoints() (points []*MountPoint) { 48 points = append(points, &MountPoint{ 49 Source: "proc", 50 Destination: "/proc", 51 Type: "proc", 52 }, &MountPoint{ 53 Source: "/dev/null", 54 Destination: "/dev/null", 55 }, &MountPoint{ 56 Source: "/dev/zero", 57 Destination: "/dev/zero", 58 }, &MountPoint{ 59 Source: "/dev/full", 60 Destination: "/dev/full", 61 }) 62 63 readOnly := []string{ 64 "/dev/random", 65 "/dev/urandom", 66 "/etc/resolv.conf", 67 "/lib", 68 "/lib32", 69 "/lib64", 70 "/usr/lib", 71 "/usr/lib32", 72 "/usr/lib64", 73 "/etc/ca-certificates", 74 "/etc/ssl/certs", 75 "/system/etc/security/cacerts", 76 "/usr/local/share/certs", 77 "/etc/pki/tls/certs", 78 "/etc/openssl/certs", 79 "/etc/ssl/ca-bundle.pem", 80 "/etc/pki/tls/cacert.pem", 81 "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", 82 } 83 84 for _, v := range []string{"SSL_CERT_FILE", "SSL_CERT_DIR"} { 85 if path := os.Getenv(v); path != "" { 86 readOnly = append(readOnly, path) 87 } 88 } 89 90 for _, point := range readOnly { 91 points = append(points, &MountPoint{ 92 Source: point, 93 Destination: point, 94 ReadOnly: true, 95 }) 96 } 97 98 return 99 } 100 101 func runProcess(config *Configuration, path string) error { 102 root, err := ioutil.TempDir("", "") 103 if err != nil { 104 return err 105 } 106 defer os.RemoveAll(root) 107 108 if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil { 109 return errors.Wrapf(err, "unable to make root private") 110 } 111 112 if err := mountMountPoints(root, systemMountPoints()); err != nil { 113 return errors.Wrapf(err, "unable to mount sandbox system mount points") 114 } 115 116 if err := mountMountPoints(root, config.MountPoints); err != nil { 117 return errors.Wrapf(err, "unable to mount sandbox config mount points") 118 } 119 120 if err := pivotRoot(root); err != nil { 121 return errors.Wrapf(err, "unable to pivot sandbox root") 122 } 123 124 if err := os.Mkdir("/tmp", 0755); err != nil { 125 return errors.Wrapf(err, "unable to create /tmp") 126 } 127 128 if config.WorkingDirectory != "" { 129 if err := os.Chdir(config.WorkingDirectory); err != nil { 130 return errors.Wrapf(err, "unable to set working directory") 131 } 132 } 133 134 if err := dropInheritableCapabilities(); err != nil { 135 return errors.Wrapf(err, "unable to drop inheritable capabilities") 136 } 137 138 if err := enableSeccompFilter(); err != nil { 139 return errors.Wrapf(err, "unable to enable seccomp filter") 140 } 141 142 return runExecutable(path) 143 } 144 145 func mountMountPoint(root string, mountPoint *MountPoint) error { 146 isDir := true 147 if mountPoint.Type == "" { 148 stat, err := os.Lstat(mountPoint.Source) 149 if err != nil { 150 return nil 151 } 152 if (stat.Mode() & os.ModeSymlink) != 0 { 153 if path, err := filepath.EvalSymlinks(mountPoint.Source); err == nil { 154 newMountPoint := *mountPoint 155 newMountPoint.Source = path 156 if err := mountMountPoint(root, &newMountPoint); err != nil { 157 return errors.Wrapf(err, "unable to mount symbolic link target: "+mountPoint.Source) 158 } 159 return nil 160 } 161 } 162 isDir = stat.IsDir() 163 } 164 165 target := filepath.Join(root, mountPoint.Destination) 166 167 if isDir { 168 if err := os.MkdirAll(target, 0755); err != nil { 169 return errors.Wrapf(err, "unable to create directory: "+target) 170 } 171 } else { 172 if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { 173 return errors.Wrapf(err, "unable to create directory: "+target) 174 } 175 f, err := os.Create(target) 176 if err != nil { 177 return errors.Wrapf(err, "unable to create file: "+target) 178 } 179 f.Close() 180 } 181 182 flags := uintptr(syscall.MS_NOSUID | syscall.MS_NODEV) 183 if mountPoint.Type == "" { 184 flags |= syscall.MS_BIND 185 } 186 if mountPoint.ReadOnly { 187 flags |= syscall.MS_RDONLY 188 } 189 190 if err := syscall.Mount(mountPoint.Source, target, mountPoint.Type, flags, ""); err != nil { 191 return errors.Wrapf(err, "unable to mount "+mountPoint.Source) 192 } 193 194 if (flags & syscall.MS_BIND) != 0 { 195 // If this was a bind mount, our other flags actually got silently ignored during the above syscall: 196 // 197 // If mountflags includes MS_BIND [...] The remaining bits in the mountflags argument are 198 // also ignored, with the exception of MS_REC. 199 // 200 // Furthermore, remounting will fail if we attempt to unset a bit that was inherited from 201 // the mount's parent: 202 // 203 // The mount(2) flags MS_RDONLY, MS_NOSUID, MS_NOEXEC, and the "atime" flags 204 // (MS_NOATIME, MS_NODIRATIME, MS_RELATIME) settings become locked when propagated from 205 // a more privileged to a less privileged mount namespace, and may not be changed in the 206 // less privileged mount namespace. 207 // 208 // So we need to get the actual flags, add our new ones, then do a remount if needed. 209 var stats syscall.Statfs_t 210 if err := syscall.Statfs(target, &stats); err != nil { 211 return errors.Wrap(err, "unable to get mount flags for target: "+target) 212 } 213 const lockedFlagsMask = unix.MS_RDONLY | unix.MS_NOSUID | unix.MS_NOEXEC | unix.MS_NOATIME | unix.MS_NODIRATIME | unix.MS_RELATIME 214 lockedFlags := uintptr(stats.Flags & lockedFlagsMask) 215 if lockedFlags != ((flags | lockedFlags) & lockedFlagsMask) { 216 if err := syscall.Mount("", target, "", flags|lockedFlags|syscall.MS_REMOUNT, ""); err != nil { 217 return errors.Wrapf(err, "unable to remount "+mountPoint.Source) 218 } 219 } 220 } 221 222 return nil 223 } 224 225 func mountMountPoints(root string, mountPoints []*MountPoint) error { 226 for _, mountPoint := range mountPoints { 227 if err := mountMountPoint(root, mountPoint); err != nil { 228 return err 229 } 230 } 231 232 return nil 233 } 234 235 func pivotRoot(newRoot string) error { 236 if err := syscall.Mount(newRoot, newRoot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { 237 return errors.Wrapf(err, "unable to mount new root") 238 } 239 240 prevRoot := filepath.Join(newRoot, ".prev_root") 241 242 if err := os.MkdirAll(prevRoot, 0700); err != nil { 243 return errors.Wrapf(err, "unable to create directory for previous root") 244 } 245 246 if err := syscall.PivotRoot(newRoot, prevRoot); err != nil { 247 return errors.Wrapf(err, "syscall error") 248 } 249 250 if err := os.Chdir("/"); err != nil { 251 return errors.Wrapf(err, "unable to change directory") 252 } 253 254 prevRoot = "/.prev_root" 255 256 if err := syscall.Unmount(prevRoot, syscall.MNT_DETACH); err != nil { 257 return errors.Wrapf(err, "unable to unmount previous root") 258 } 259 260 if err := os.RemoveAll(prevRoot); err != nil { 261 return errors.Wrapf(err, "unable to remove previous root directory") 262 } 263 264 return nil 265 } 266 267 func dropInheritableCapabilities() error { 268 type capHeader struct { 269 version uint32 270 pid int 271 } 272 273 type capData struct { 274 effective uint32 275 permitted uint32 276 inheritable uint32 277 } 278 279 var hdr capHeader 280 var data [2]capData 281 282 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&hdr)), 0, 0); errno != 0 { 283 return errors.Wrapf(syscall.Errno(errno), "unable to get capabilities version") 284 } 285 286 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&hdr)), uintptr(unsafe.Pointer(&data[0])), 0); errno != 0 { 287 return errors.Wrapf(syscall.Errno(errno), "unable to get capabilities") 288 } 289 290 data[0].inheritable = 0 291 data[1].inheritable = 0 292 if _, _, errno := syscall.Syscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(&hdr)), uintptr(unsafe.Pointer(&data[0])), 0); errno != 0 { 293 return errors.Wrapf(syscall.Errno(errno), "unable to set inheritable capabilities") 294 } 295 296 for i := 0; i < 64; i++ { 297 if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_CAPBSET_DROP, uintptr(i), 0); errno != 0 && errno != syscall.EINVAL { 298 return errors.Wrapf(syscall.Errno(errno), "unable to drop bounding set capability") 299 } 300 } 301 302 return nil 303 } 304 305 func enableSeccompFilter() error { 306 return EnableSeccompFilter(SeccompFilter(NATIVE_AUDIT_ARCH, AllowedSyscalls)) 307 } 308 309 func runExecutable(path string) error { 310 childFiles := []*os.File{ 311 os.NewFile(3, ""), os.NewFile(4, ""), 312 } 313 defer childFiles[0].Close() 314 defer childFiles[1].Close() 315 316 cmd := exec.Command(path) 317 cmd.Stdout = os.Stdout 318 cmd.Stderr = os.Stderr 319 cmd.ExtraFiles = childFiles 320 cmd.SysProcAttr = &syscall.SysProcAttr{ 321 Pdeathsig: syscall.SIGTERM, 322 } 323 324 if err := cmd.Run(); err != nil { 325 return err 326 } 327 328 return nil 329 } 330 331 type process struct { 332 command *exec.Cmd 333 } 334 335 func newProcess(ctx context.Context, config *Configuration, path string) (rpcplugin.Process, io.ReadWriteCloser, error) { 336 configJSON, err := json.Marshal(config) 337 if err != nil { 338 return nil, nil, err 339 } 340 341 ipc, childFiles, err := rpcplugin.NewIPC() 342 if err != nil { 343 return nil, nil, err 344 } 345 defer childFiles[0].Close() 346 defer childFiles[1].Close() 347 348 cmd := exec.CommandContext(ctx, "/proc/self/exe") 349 cmd.Args = []string{"sandbox.runProcess", string(configJSON), path} 350 cmd.Stdout = os.Stdout 351 cmd.Stderr = os.Stderr 352 cmd.ExtraFiles = childFiles 353 354 cmd.SysProcAttr = &syscall.SysProcAttr{ 355 Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER, 356 Pdeathsig: syscall.SIGTERM, 357 GidMappings: []syscall.SysProcIDMap{ 358 { 359 ContainerID: 0, 360 HostID: os.Getgid(), 361 Size: 1, 362 }, 363 }, 364 UidMappings: []syscall.SysProcIDMap{ 365 { 366 ContainerID: 0, 367 HostID: os.Getuid(), 368 Size: 1, 369 }, 370 }, 371 } 372 373 err = cmd.Start() 374 if err != nil { 375 ipc.Close() 376 return nil, nil, err 377 } 378 379 return &process{ 380 command: cmd, 381 }, ipc, nil 382 } 383 384 func (p *process) Wait() error { 385 return p.command.Wait() 386 } 387 388 func init() { 389 if len(os.Args) < 1 || os.Args[0] != "sandbox.checkSupportInNamespace" { 390 return 391 } 392 393 if err := checkSupportInNamespace(); err != nil { 394 fmt.Fprintf(os.Stderr, "%v", err.Error()) 395 os.Exit(1) 396 } 397 398 os.Exit(0) 399 } 400 401 func checkSupportInNamespace() error { 402 root, err := ioutil.TempDir("", "") 403 if err != nil { 404 return err 405 } 406 defer os.RemoveAll(root) 407 408 if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil { 409 return errors.Wrapf(err, "unable to make root private") 410 } 411 412 if err := mountMountPoints(root, systemMountPoints()); err != nil { 413 return errors.Wrapf(err, "unable to mount sandbox system mount points") 414 } 415 416 if err := pivotRoot(root); err != nil { 417 return errors.Wrapf(err, "unable to pivot sandbox root") 418 } 419 420 if err := dropInheritableCapabilities(); err != nil { 421 return errors.Wrapf(err, "unable to drop inheritable capabilities") 422 } 423 424 if err := enableSeccompFilter(); err != nil { 425 return errors.Wrapf(err, "unable to enable seccomp filter") 426 } 427 428 return nil 429 } 430 431 func checkSupport() error { 432 if AllowedSyscalls == nil { 433 return fmt.Errorf("unsupported architecture") 434 } 435 436 stderr := &bytes.Buffer{} 437 438 cmd := exec.Command("/proc/self/exe") 439 cmd.Args = []string{"sandbox.checkSupportInNamespace"} 440 cmd.Stderr = stderr 441 cmd.SysProcAttr = &syscall.SysProcAttr{ 442 Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER, 443 Pdeathsig: syscall.SIGTERM, 444 GidMappings: []syscall.SysProcIDMap{ 445 { 446 ContainerID: 0, 447 HostID: os.Getgid(), 448 Size: 1, 449 }, 450 }, 451 UidMappings: []syscall.SysProcIDMap{ 452 { 453 ContainerID: 0, 454 HostID: os.Getuid(), 455 Size: 1, 456 }, 457 }, 458 } 459 460 if err := cmd.Start(); err != nil { 461 return errors.Wrapf(err, "unable to create user namespace") 462 } 463 464 if err := cmd.Wait(); err != nil { 465 if _, ok := err.(*exec.ExitError); ok { 466 return errors.Wrapf(fmt.Errorf("%v", stderr.String()), "unable to prepare namespace") 467 } 468 return errors.Wrapf(err, "unable to prepare namespace") 469 } 470 471 return nil 472 }