gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/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  	"gvisor.dev/gvisor/pkg/abi/linux"
    23  	"gvisor.dev/gvisor/pkg/context"
    24  	"gvisor.dev/gvisor/pkg/fspath"
    25  	"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
    26  	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
    27  	"gvisor.dev/gvisor/pkg/sentry/kernel/contexttest"
    28  	"gvisor.dev/gvisor/pkg/sentry/vfs"
    29  	"gvisor.dev/gvisor/pkg/usermem"
    30  )
    31  
    32  // createEtcPasswd creates /etc/passwd with the given contents and mode. If
    33  // mode is empty, then no file will be created. If mode is not a regular file
    34  // mode, then contents is ignored.
    35  func createEtcPasswd(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, root vfs.VirtualDentry, contents string, mode linux.FileMode) error {
    36  	pop := vfs.PathOperation{
    37  		Root:  root,
    38  		Start: root,
    39  		Path:  fspath.Parse("etc"),
    40  	}
    41  	if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
    42  		Mode: 0755,
    43  	}); err != nil {
    44  		return fmt.Errorf("failed to create directory etc: %v", err)
    45  	}
    46  
    47  	pop = vfs.PathOperation{
    48  		Root:  root,
    49  		Start: root,
    50  		Path:  fspath.Parse("etc/passwd"),
    51  	}
    52  	switch mode.FileType() {
    53  	case 0:
    54  		// Don't create anything.
    55  		return nil
    56  	case linux.S_IFREG:
    57  		fd, err := vfsObj.OpenAt(ctx, creds, &pop, &vfs.OpenOptions{Flags: linux.O_CREAT | linux.O_WRONLY, Mode: mode})
    58  		if err != nil {
    59  			return err
    60  		}
    61  		defer fd.DecRef(ctx)
    62  		_, err = fd.Write(ctx, usermem.BytesIOSequence([]byte(contents)), vfs.WriteOptions{})
    63  		return err
    64  	case linux.S_IFDIR:
    65  		return vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{Mode: mode})
    66  	case linux.S_IFIFO:
    67  		return vfsObj.MknodAt(ctx, creds, &pop, &vfs.MknodOptions{Mode: mode})
    68  	default:
    69  		return fmt.Errorf("unknown file type %x", mode.FileType())
    70  	}
    71  }
    72  
    73  // TestGetExecUserHome tests the getExecUserHome function.
    74  func TestGetExecUserHome(t *testing.T) {
    75  	tests := map[string]struct {
    76  		uid            auth.KUID
    77  		passwdContents string
    78  		passwdMode     linux.FileMode
    79  		expected       string
    80  	}{
    81  		"success": {
    82  			uid:            1000,
    83  			passwdContents: "adin::1000:1111::/home/adin:/bin/sh",
    84  			passwdMode:     linux.S_IFREG | 0666,
    85  			expected:       "/home/adin",
    86  		},
    87  		"no_perms": {
    88  			uid:            1000,
    89  			passwdContents: "adin::1000:1111::/home/adin:/bin/sh",
    90  			passwdMode:     linux.S_IFREG,
    91  			expected:       "/",
    92  		},
    93  		"no_passwd": {
    94  			uid:      1000,
    95  			expected: "/",
    96  		},
    97  		"directory": {
    98  			uid:        1000,
    99  			passwdMode: linux.S_IFDIR | 0666,
   100  			expected:   "/",
   101  		},
   102  		// Currently we don't allow named pipes.
   103  		"named_pipe": {
   104  			uid:        1000,
   105  			passwdMode: linux.S_IFIFO | 0666,
   106  			expected:   "/",
   107  		},
   108  	}
   109  
   110  	for name, tc := range tests {
   111  		t.Run(name, func(t *testing.T) {
   112  			ctx := contexttest.Context(t)
   113  			creds := auth.CredentialsFromContext(ctx)
   114  
   115  			// Create VFS.
   116  			vfsObj := vfs.VirtualFilesystem{}
   117  			if err := vfsObj.Init(ctx); err != nil {
   118  				t.Fatalf("VFS init: %v", err)
   119  			}
   120  			vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
   121  				AllowUserMount: true,
   122  			})
   123  			mns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil)
   124  			if err != nil {
   125  				t.Fatalf("failed to create tmpfs root mount: %v", err)
   126  			}
   127  			defer mns.DecRef(ctx)
   128  			root := mns.Root(ctx)
   129  			defer root.DecRef(ctx)
   130  
   131  			if err := createEtcPasswd(ctx, &vfsObj, creds, root, tc.passwdContents, tc.passwdMode); err != nil {
   132  				t.Fatalf("createEtcPasswd failed: %v", err)
   133  			}
   134  
   135  			got, err := getExecUserHome(ctx, mns, tc.uid)
   136  			if err != nil {
   137  				t.Fatalf("failed to get user home: %v", err)
   138  			}
   139  
   140  			if got != tc.expected {
   141  				t.Fatalf("expected %v, got: %v", tc.expected, got)
   142  			}
   143  		})
   144  	}
   145  }
   146  
   147  // TestFindHomeInPasswd tests the findHomeInPasswd function's passwd file parsing.
   148  func TestFindHomeInPasswd(t *testing.T) {
   149  	tests := map[string]struct {
   150  		uid      uint32
   151  		passwd   string
   152  		expected string
   153  		def      string
   154  	}{
   155  		"empty": {
   156  			uid:      1000,
   157  			passwd:   "",
   158  			expected: "/",
   159  			def:      "/",
   160  		},
   161  		"whitespace": {
   162  			uid:      1000,
   163  			passwd:   "       ",
   164  			expected: "/",
   165  			def:      "/",
   166  		},
   167  		"full": {
   168  			uid:      1000,
   169  			passwd:   "adin::1000:1111::/home/adin:/bin/sh",
   170  			expected: "/home/adin",
   171  			def:      "/",
   172  		},
   173  		// For better or worse, this is how runc works.
   174  		"partial": {
   175  			uid:      1000,
   176  			passwd:   "adin::1000:1111:",
   177  			expected: "",
   178  			def:      "/",
   179  		},
   180  		"multiple": {
   181  			uid:      1001,
   182  			passwd:   "adin::1000:1111::/home/adin:/bin/sh\nian::1001:1111::/home/ian:/bin/sh",
   183  			expected: "/home/ian",
   184  			def:      "/",
   185  		},
   186  		"duplicate": {
   187  			uid:      1000,
   188  			passwd:   "adin::1000:1111::/home/adin:/bin/sh\nian::1000:1111::/home/ian:/bin/sh",
   189  			expected: "/home/adin",
   190  			def:      "/",
   191  		},
   192  		"empty_lines": {
   193  			uid:      1001,
   194  			passwd:   "adin::1000:1111::/home/adin:/bin/sh\n\n\nian::1001:1111::/home/ian:/bin/sh",
   195  			expected: "/home/ian",
   196  			def:      "/",
   197  		},
   198  	}
   199  
   200  	for name, tc := range tests {
   201  		t.Run(name, func(t *testing.T) {
   202  			got, err := findHomeInPasswd(tc.uid, strings.NewReader(tc.passwd), tc.def)
   203  			if err != nil {
   204  				t.Fatalf("error parsing passwd: %v", err)
   205  			}
   206  			if tc.expected != got {
   207  				t.Fatalf("expected %v, got: %v", tc.expected, got)
   208  			}
   209  		})
   210  	}
   211  }
   212  
   213  // TestGetExecUIDGIDFromUser tests the GetExecUIDGIDFromUser function.
   214  func TestGetExecUIDGIDFromUser(t *testing.T) {
   215  	tests := map[string]struct {
   216  		user           string
   217  		passwdContents string
   218  		passwdMode     linux.FileMode
   219  		expectedUID    auth.KUID
   220  		expectedGID    auth.KGID
   221  	}{
   222  		"success": {
   223  			user:           "user0",
   224  			passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh",
   225  			passwdMode:     linux.S_IFREG | 0666,
   226  			expectedUID:    1000,
   227  			expectedGID:    1111,
   228  		},
   229  		"success_with_uid_only": {
   230  			user:           "1000",
   231  			passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh",
   232  			passwdMode:     linux.S_IFREG | 0666,
   233  			expectedUID:    1000,
   234  			expectedGID:    1111,
   235  		},
   236  		"success_with_uid_and_gid": {
   237  			user:           "1000:1111",
   238  			passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh",
   239  			passwdMode:     linux.S_IFREG | 0666,
   240  			expectedUID:    1000,
   241  			expectedGID:    1111,
   242  		},
   243  		"success_with_uid_and_wrong_gid": {
   244  			user:           "1000:1112",
   245  			passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh",
   246  			passwdMode:     linux.S_IFREG | 0666,
   247  			expectedUID:    1000,
   248  			expectedGID:    1111,
   249  		},
   250  		"no_user": {
   251  			user:           "user1",
   252  			passwdContents: "user0::1000:1111::/home/user0:/bin/sh",
   253  			passwdMode:     linux.S_IFREG | 0666,
   254  			expectedUID:    65534,
   255  			expectedGID:    65534,
   256  		},
   257  		"multiple_user_no_match": {
   258  			user:           "user1",
   259  			passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
   260  			passwdMode:     linux.S_IFREG | 0666,
   261  			expectedUID:    65534,
   262  			expectedGID:    65534,
   263  		},
   264  		"multiple_user_many_match": {
   265  			user:           "user1",
   266  			passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser1::1002:1112::/home/user1:/bin/sh\nuser1::1003:1113::/home/user1:/bin/sh",
   267  			passwdMode:     linux.S_IFREG | 0666,
   268  			expectedUID:    65534,
   269  			expectedGID:    65534,
   270  		},
   271  		"invalid_file": {
   272  			user:           "user1",
   273  			passwdContents: "user0:1000:1111::/home/user0:/bin/sh\nuser1::1001:1111::/home/user1:/bin/sh\nuser2::/home/user2:/bin/sh",
   274  			passwdMode:     linux.S_IFREG | 0666,
   275  			expectedUID:    65534,
   276  			expectedGID:    65534,
   277  		},
   278  		"empty_file": {
   279  			user:           "user1",
   280  			passwdContents: "",
   281  			passwdMode:     linux.S_IFREG | 0666,
   282  			expectedUID:    65534,
   283  			expectedGID:    65534,
   284  		},
   285  		"empty_user": {
   286  			user:           "",
   287  			passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
   288  			passwdMode:     linux.S_IFREG | 0666,
   289  			expectedUID:    65534,
   290  			expectedGID:    65534,
   291  		},
   292  	}
   293  
   294  	for name, tc := range tests {
   295  		t.Run(name, func(t *testing.T) {
   296  			ctx := contexttest.Context(t)
   297  			creds := auth.CredentialsFromContext(ctx)
   298  
   299  			// Create VFS.
   300  			vfsObj := vfs.VirtualFilesystem{}
   301  			if err := vfsObj.Init(ctx); err != nil {
   302  				t.Fatalf("VFS init: %v", err)
   303  			}
   304  			vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
   305  				AllowUserMount: true,
   306  			})
   307  			mns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil)
   308  			if err != nil {
   309  				t.Fatalf("failed to create tmpfs root mount: %v", err)
   310  			}
   311  			defer mns.DecRef(ctx)
   312  			root := mns.Root(ctx)
   313  			defer root.DecRef(ctx)
   314  
   315  			if err := createEtcPasswd(ctx, &vfsObj, creds, root, tc.passwdContents, tc.passwdMode); err != nil {
   316  				t.Fatalf("createEtcPasswd failed: %v", err)
   317  			}
   318  
   319  			gotUID, gotGID, err := GetExecUIDGIDFromUser(ctx, mns, tc.user)
   320  			if strings.HasPrefix(name, "success") {
   321  				if err != nil {
   322  					t.Fatalf("failed to get UID and GID from user: %v %v", tc.user, err)
   323  				}
   324  			} else {
   325  				if err == nil {
   326  					t.Fatalf("retrieved UID and GID when user %v is not in /etc/passwd: %v", tc.user, err)
   327  				}
   328  			}
   329  			if gotUID != tc.expectedUID {
   330  				t.Fatalf("expectedUID %v, gotUID: %v", tc.expectedUID, gotUID)
   331  			}
   332  			if gotGID != tc.expectedGID {
   333  				t.Fatalf("expectedGID %v, gotGID: %v", tc.expectedGID, gotGID)
   334  			}
   335  		})
   336  	}
   337  }