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  }