github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/user/user_test.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
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    23  	"github.com/SagerNet/gvisor/pkg/context"
    24  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    25  	"github.com/SagerNet/gvisor/pkg/sentry/fs/tmpfs"
    26  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/auth"
    27  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/contexttest"
    28  	"github.com/SagerNet/gvisor/pkg/usermem"
    29  )
    30  
    31  // createEtcPasswd creates /etc/passwd with the given contents and mode. If
    32  // mode is empty, then no file will be created. If mode is not a regular file
    33  // mode, then contents is ignored.
    34  func createEtcPasswd(ctx context.Context, root *fs.Dirent, contents string, mode linux.FileMode) error {
    35  	if err := root.CreateDirectory(ctx, root, "etc", fs.FilePermsFromMode(0755)); err != nil {
    36  		return err
    37  	}
    38  	etc, err := root.Walk(ctx, root, "etc")
    39  	if err != nil {
    40  		return err
    41  	}
    42  	defer etc.DecRef(ctx)
    43  	switch mode.FileType() {
    44  	case 0:
    45  		// Don't create anything.
    46  		return nil
    47  	case linux.S_IFREG:
    48  		passwd, err := etc.Create(ctx, root, "passwd", fs.FileFlags{Write: true}, fs.FilePermsFromMode(mode))
    49  		if err != nil {
    50  			return err
    51  		}
    52  		defer passwd.DecRef(ctx)
    53  		if _, err := passwd.Writev(ctx, usermem.BytesIOSequence([]byte(contents))); err != nil {
    54  			return err
    55  		}
    56  		return nil
    57  	case linux.S_IFDIR:
    58  		return etc.CreateDirectory(ctx, root, "passwd", fs.FilePermsFromMode(mode))
    59  	case linux.S_IFIFO:
    60  		return etc.CreateFifo(ctx, root, "passwd", fs.FilePermsFromMode(mode))
    61  	default:
    62  		return fmt.Errorf("unknown file type %x", mode.FileType())
    63  	}
    64  }
    65  
    66  // TestGetExecUserHome tests the getExecUserHome function.
    67  func TestGetExecUserHome(t *testing.T) {
    68  	tests := map[string]struct {
    69  		uid            auth.KUID
    70  		passwdContents string
    71  		passwdMode     linux.FileMode
    72  		expected       string
    73  	}{
    74  		"success": {
    75  			uid:            1000,
    76  			passwdContents: "adin::1000:1111::/home/adin:/bin/sh",
    77  			passwdMode:     linux.S_IFREG | 0666,
    78  			expected:       "/home/adin",
    79  		},
    80  		"no_perms": {
    81  			uid:            1000,
    82  			passwdContents: "adin::1000:1111::/home/adin:/bin/sh",
    83  			passwdMode:     linux.S_IFREG,
    84  			expected:       "/",
    85  		},
    86  		"no_passwd": {
    87  			uid:      1000,
    88  			expected: "/",
    89  		},
    90  		"directory": {
    91  			uid:        1000,
    92  			passwdMode: linux.S_IFDIR | 0666,
    93  			expected:   "/",
    94  		},
    95  		// Currently we don't allow named pipes.
    96  		"named_pipe": {
    97  			uid:        1000,
    98  			passwdMode: linux.S_IFIFO | 0666,
    99  			expected:   "/",
   100  		},
   101  	}
   102  
   103  	for name, tc := range tests {
   104  		t.Run(name, func(t *testing.T) {
   105  			ctx := contexttest.Context(t)
   106  			msrc := fs.NewPseudoMountSource(ctx)
   107  			rootInode, err := tmpfs.NewDir(ctx, nil, fs.RootOwner, fs.FilePermsFromMode(0777), msrc, nil /* parent */)
   108  			if err != nil {
   109  				t.Fatalf("tmpfs.NewDir failed: %v", err)
   110  			}
   111  
   112  			mns, err := fs.NewMountNamespace(ctx, rootInode)
   113  			if err != nil {
   114  				t.Fatalf("NewMountNamespace failed: %v", err)
   115  			}
   116  			defer mns.DecRef(ctx)
   117  			root := mns.Root()
   118  			defer root.DecRef(ctx)
   119  			ctx = fs.WithRoot(ctx, root)
   120  
   121  			if err := createEtcPasswd(ctx, root, tc.passwdContents, tc.passwdMode); err != nil {
   122  				t.Fatalf("createEtcPasswd failed: %v", err)
   123  			}
   124  
   125  			got, err := getExecUserHome(ctx, mns, tc.uid)
   126  			if err != nil {
   127  				t.Fatalf("failed to get user home: %v", err)
   128  			}
   129  
   130  			if got != tc.expected {
   131  				t.Fatalf("expected %v, got: %v", tc.expected, got)
   132  			}
   133  		})
   134  	}
   135  }
   136  
   137  // TestFindHomeInPasswd tests the findHomeInPasswd function's passwd file parsing.
   138  func TestFindHomeInPasswd(t *testing.T) {
   139  	tests := map[string]struct {
   140  		uid      uint32
   141  		passwd   string
   142  		expected string
   143  		def      string
   144  	}{
   145  		"empty": {
   146  			uid:      1000,
   147  			passwd:   "",
   148  			expected: "/",
   149  			def:      "/",
   150  		},
   151  		"whitespace": {
   152  			uid:      1000,
   153  			passwd:   "       ",
   154  			expected: "/",
   155  			def:      "/",
   156  		},
   157  		"full": {
   158  			uid:      1000,
   159  			passwd:   "adin::1000:1111::/home/adin:/bin/sh",
   160  			expected: "/home/adin",
   161  			def:      "/",
   162  		},
   163  		// For better or worse, this is how runc works.
   164  		"partial": {
   165  			uid:      1000,
   166  			passwd:   "adin::1000:1111:",
   167  			expected: "",
   168  			def:      "/",
   169  		},
   170  		"multiple": {
   171  			uid:      1001,
   172  			passwd:   "adin::1000:1111::/home/adin:/bin/sh\nian::1001:1111::/home/ian:/bin/sh",
   173  			expected: "/home/ian",
   174  			def:      "/",
   175  		},
   176  		"duplicate": {
   177  			uid:      1000,
   178  			passwd:   "adin::1000:1111::/home/adin:/bin/sh\nian::1000:1111::/home/ian:/bin/sh",
   179  			expected: "/home/adin",
   180  			def:      "/",
   181  		},
   182  		"empty_lines": {
   183  			uid:      1001,
   184  			passwd:   "adin::1000:1111::/home/adin:/bin/sh\n\n\nian::1001:1111::/home/ian:/bin/sh",
   185  			expected: "/home/ian",
   186  			def:      "/",
   187  		},
   188  	}
   189  
   190  	for name, tc := range tests {
   191  		t.Run(name, func(t *testing.T) {
   192  			got, err := findHomeInPasswd(tc.uid, strings.NewReader(tc.passwd), tc.def)
   193  			if err != nil {
   194  				t.Fatalf("error parsing passwd: %v", err)
   195  			}
   196  			if tc.expected != got {
   197  				t.Fatalf("expected %v, got: %v", tc.expected, got)
   198  			}
   199  		})
   200  	}
   201  }