github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/user/user.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package user contains methods for resolving filesystem paths based on the
    16  // user and their environment.
    17  package user
    18  
    19  import (
    20  	"bufio"
    21  	"fmt"
    22  	"io"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    27  	"github.com/SagerNet/gvisor/pkg/context"
    28  	"github.com/SagerNet/gvisor/pkg/fspath"
    29  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    30  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/auth"
    31  	"github.com/SagerNet/gvisor/pkg/sentry/vfs"
    32  	"github.com/SagerNet/gvisor/pkg/usermem"
    33  )
    34  
    35  type fileReader struct {
    36  	// Ctx is the context for the file reader.
    37  	Ctx context.Context
    38  
    39  	// File is the file to read from.
    40  	File *fs.File
    41  }
    42  
    43  // Read implements io.Reader.Read.
    44  func (r *fileReader) Read(buf []byte) (int, error) {
    45  	n, err := r.File.Readv(r.Ctx, usermem.BytesIOSequence(buf))
    46  	return int(n), err
    47  }
    48  
    49  // getExecUserHome returns the home directory of the executing user read from
    50  // /etc/passwd as read from the container filesystem.
    51  func getExecUserHome(ctx context.Context, rootMns *fs.MountNamespace, uid auth.KUID) (string, error) {
    52  	// The default user home directory to return if no user matching the user
    53  	// if found in the /etc/passwd found in the image.
    54  	const defaultHome = "/"
    55  
    56  	// Open the /etc/passwd file from the dirent via the root mount namespace.
    57  	mnsRoot := rootMns.Root()
    58  	maxTraversals := uint(linux.MaxSymlinkTraversals)
    59  	dirent, err := rootMns.FindInode(ctx, mnsRoot, nil, "/etc/passwd", &maxTraversals)
    60  	if err != nil {
    61  		// NOTE: Ignore errors opening the passwd file. If the passwd file
    62  		// doesn't exist we will return the default home directory.
    63  		return defaultHome, nil
    64  	}
    65  	defer dirent.DecRef(ctx)
    66  
    67  	// Check read permissions on the file.
    68  	if err := dirent.Inode.CheckPermission(ctx, fs.PermMask{Read: true}); err != nil {
    69  		// NOTE: Ignore permissions errors here and return default root dir.
    70  		return defaultHome, nil
    71  	}
    72  
    73  	// Only open regular files. We don't open other files like named pipes as
    74  	// they may block and might present some attack surface to the container.
    75  	// Note that runc does not seem to do this kind of checking.
    76  	if !fs.IsRegular(dirent.Inode.StableAttr) {
    77  		return defaultHome, nil
    78  	}
    79  
    80  	f, err := dirent.Inode.GetFile(ctx, dirent, fs.FileFlags{Read: true, Directory: false})
    81  	if err != nil {
    82  		return "", err
    83  	}
    84  	defer f.DecRef(ctx)
    85  
    86  	r := &fileReader{
    87  		Ctx:  ctx,
    88  		File: f,
    89  	}
    90  
    91  	return findHomeInPasswd(uint32(uid), r, defaultHome)
    92  }
    93  
    94  type fileReaderVFS2 struct {
    95  	ctx context.Context
    96  	fd  *vfs.FileDescription
    97  }
    98  
    99  func (r *fileReaderVFS2) Read(buf []byte) (int, error) {
   100  	n, err := r.fd.Read(r.ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
   101  	return int(n), err
   102  }
   103  
   104  func getExecUserHomeVFS2(ctx context.Context, mns *vfs.MountNamespace, uid auth.KUID) (string, error) {
   105  	const defaultHome = "/"
   106  
   107  	root := mns.Root()
   108  	root.IncRef()
   109  	defer root.DecRef(ctx)
   110  
   111  	creds := auth.CredentialsFromContext(ctx)
   112  
   113  	target := &vfs.PathOperation{
   114  		Root:  root,
   115  		Start: root,
   116  		Path:  fspath.Parse("/etc/passwd"),
   117  	}
   118  
   119  	opts := &vfs.OpenOptions{
   120  		Flags: linux.O_RDONLY,
   121  	}
   122  
   123  	fd, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, target, opts)
   124  	if err != nil {
   125  		return defaultHome, nil
   126  	}
   127  	defer fd.DecRef(ctx)
   128  
   129  	r := &fileReaderVFS2{
   130  		ctx: ctx,
   131  		fd:  fd,
   132  	}
   133  
   134  	homeDir, err := findHomeInPasswd(uint32(uid), r, defaultHome)
   135  	if err != nil {
   136  		return "", err
   137  	}
   138  
   139  	return homeDir, nil
   140  }
   141  
   142  // MaybeAddExecUserHome returns a new slice with the HOME enviroment variable
   143  // set if the slice does not already contain it, otherwise it returns the
   144  // original slice unmodified.
   145  func MaybeAddExecUserHome(ctx context.Context, mns *fs.MountNamespace, uid auth.KUID, envv []string) ([]string, error) {
   146  	// Check if the envv already contains HOME.
   147  	for _, env := range envv {
   148  		if strings.HasPrefix(env, "HOME=") {
   149  			// We have it. Return the original slice unmodified.
   150  			return envv, nil
   151  		}
   152  	}
   153  
   154  	// Read /etc/passwd for the user's HOME directory and set the HOME
   155  	// environment variable as required by POSIX if it is not overridden by
   156  	// the user.
   157  	homeDir, err := getExecUserHome(ctx, mns, uid)
   158  	if err != nil {
   159  		return nil, fmt.Errorf("error reading exec user: %v", err)
   160  	}
   161  
   162  	return append(envv, "HOME="+homeDir), nil
   163  }
   164  
   165  // MaybeAddExecUserHomeVFS2 returns a new slice with the HOME enviroment
   166  // variable set if the slice does not already contain it, otherwise it returns
   167  // the original slice unmodified.
   168  func MaybeAddExecUserHomeVFS2(ctx context.Context, vmns *vfs.MountNamespace, uid auth.KUID, envv []string) ([]string, error) {
   169  	// Check if the envv already contains HOME.
   170  	for _, env := range envv {
   171  		if strings.HasPrefix(env, "HOME=") {
   172  			// We have it. Return the original slice unmodified.
   173  			return envv, nil
   174  		}
   175  	}
   176  
   177  	// Read /etc/passwd for the user's HOME directory and set the HOME
   178  	// environment variable as required by POSIX if it is not overridden by
   179  	// the user.
   180  	homeDir, err := getExecUserHomeVFS2(ctx, vmns, uid)
   181  	if err != nil {
   182  		return nil, fmt.Errorf("error reading exec user: %v", err)
   183  	}
   184  	return append(envv, "HOME="+homeDir), nil
   185  }
   186  
   187  // findHomeInPasswd parses a passwd file and returns the given user's home
   188  // directory. This function does it's best to replicate the runc's behavior.
   189  func findHomeInPasswd(uid uint32, passwd io.Reader, defaultHome string) (string, error) {
   190  	s := bufio.NewScanner(passwd)
   191  
   192  	for s.Scan() {
   193  		if err := s.Err(); err != nil {
   194  			return "", err
   195  		}
   196  
   197  		line := strings.TrimSpace(s.Text())
   198  		if line == "" {
   199  			continue
   200  		}
   201  
   202  		// Pull out part of passwd entry. Loosely parse the passwd entry as some
   203  		// passwd files could be poorly written and for compatibility with runc.
   204  		//
   205  		// Per 'man 5 passwd'
   206  		// /etc/passwd contains one line for each user account, with seven
   207  		// fields delimited by colons (“:”). These fields are:
   208  		//
   209  		// - login name
   210  		// - optional encrypted password
   211  		// - numerical user ID
   212  		// - numerical group ID
   213  		// - user name or comment field
   214  		// - user home directory
   215  		// - optional user command interpreter
   216  		parts := strings.Split(line, ":")
   217  
   218  		found := false
   219  		homeDir := ""
   220  		for i, p := range parts {
   221  			switch i {
   222  			case 2:
   223  				parsedUID, err := strconv.ParseUint(p, 10, 32)
   224  				if err == nil && parsedUID == uint64(uid) {
   225  					found = true
   226  				}
   227  			case 5:
   228  				homeDir = p
   229  			}
   230  		}
   231  		if found {
   232  			// NOTE: If the uid is present but the home directory is not
   233  			// present in the /etc/passwd entry we return an empty string. This
   234  			// is, for better or worse, what runc does.
   235  			return homeDir, nil
   236  		}
   237  	}
   238  
   239  	return defaultHome, nil
   240  }