mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Handle AT_SYMLINK_NOFOLLOW flag for execveat.
PiperOrigin-RevId: 276441249
This commit is contained in:
@@ -805,7 +805,7 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID,
|
||||
// Create a fresh task context.
|
||||
remainingTraversals = uint(args.MaxSymlinkTraversals)
|
||||
|
||||
tc, se := k.LoadTaskImage(ctx, mounts, root, wd, &remainingTraversals, args.Filename, args.File, args.Argv, args.Envv, k.featureSet)
|
||||
tc, se := k.LoadTaskImage(ctx, mounts, root, wd, &remainingTraversals, args.Filename, args.File, args.Argv, args.Envv, true /*resolveFinal*/, k.featureSet)
|
||||
if se != nil {
|
||||
return nil, 0, errors.New(se.String())
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ func (t *Task) Stack() *arch.Stack {
|
||||
// * argv: Binary argv
|
||||
// * envv: Binary envv
|
||||
// * fs: Binary FeatureSet
|
||||
func (k *Kernel) LoadTaskImage(ctx context.Context, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, filename string, file *fs.File, argv, envv []string, fs *cpuid.FeatureSet) (*TaskContext, *syserr.Error) {
|
||||
func (k *Kernel) LoadTaskImage(ctx context.Context, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, filename string, file *fs.File, argv, envv []string, resolveFinal bool, fs *cpuid.FeatureSet) (*TaskContext, *syserr.Error) {
|
||||
// If File is not nil, we should load that instead of resolving filename.
|
||||
if file != nil {
|
||||
filename = file.MappedName(ctx)
|
||||
@@ -155,7 +155,7 @@ func (k *Kernel) LoadTaskImage(ctx context.Context, mounts *fs.MountNamespace, r
|
||||
m := mm.NewMemoryManager(k, k)
|
||||
defer m.DecUsers(ctx)
|
||||
|
||||
os, ac, name, err := loader.Load(ctx, m, mounts, root, wd, maxTraversals, fs, filename, file, argv, envv, k.extraAuxv, k.vdso)
|
||||
os, ac, name, err := loader.Load(ctx, m, mounts, root, wd, maxTraversals, fs, filename, file, argv, envv, resolveFinal, k.extraAuxv, k.vdso)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -640,7 +640,7 @@ func loadELF(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace
|
||||
|
||||
var interp loadedELF
|
||||
if bin.interpreter != "" {
|
||||
d, i, err := openPath(ctx, mounts, root, wd, maxTraversals, bin.interpreter)
|
||||
d, i, err := openPath(ctx, mounts, root, wd, maxTraversals, bin.interpreter, true /*resolveFinal*/)
|
||||
if err != nil {
|
||||
ctx.Infof("Error opening interpreter %s: %v", bin.interpreter, err)
|
||||
return loadedELF{}, nil, err
|
||||
|
||||
@@ -57,13 +57,19 @@ func readFull(ctx context.Context, f *fs.File, dst usermem.IOSequence, offset in
|
||||
// installed in the Task FDTable. The caller takes ownership of both.
|
||||
//
|
||||
// name must be a readable, executable, regular file.
|
||||
func openPath(ctx context.Context, mm *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, name string) (*fs.Dirent, *fs.File, error) {
|
||||
func openPath(ctx context.Context, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, name string, resolveFinal bool) (*fs.Dirent, *fs.File, error) {
|
||||
var err error
|
||||
if name == "" {
|
||||
ctx.Infof("cannot open empty name")
|
||||
return nil, nil, syserror.ENOENT
|
||||
}
|
||||
|
||||
d, err := mm.FindInode(ctx, root, wd, name, maxTraversals)
|
||||
var d *fs.Dirent
|
||||
if resolveFinal {
|
||||
d, err = mounts.FindInode(ctx, root, wd, name, maxTraversals)
|
||||
} else {
|
||||
d, err = mounts.FindLink(ctx, root, wd, name, maxTraversals)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -71,10 +77,13 @@ func openPath(ctx context.Context, mm *fs.MountNamespace, root, wd *fs.Dirent, m
|
||||
// Open file will take a reference to Dirent, so destroy this one.
|
||||
defer d.DecRef()
|
||||
|
||||
if !resolveFinal && fs.IsSymlink(d.Inode.StableAttr) {
|
||||
return nil, nil, syserror.ELOOP
|
||||
}
|
||||
|
||||
return openFile(ctx, nil, d, name)
|
||||
}
|
||||
|
||||
// openFile performs checks on a file to be executed. If provided a *fs.File,
|
||||
// openFile takes that file's Dirent and performs checks on it. If provided a
|
||||
// *fs.Dirent and not a *fs.File, it creates a *fs.File object from the Dirent's
|
||||
// Inode and performs checks on that.
|
||||
@@ -181,7 +190,7 @@ const (
|
||||
// * arch.Context matching the binary arch
|
||||
// * fs.Dirent of the binary file
|
||||
// * Possibly updated argv
|
||||
func loadBinary(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, remainingTraversals *uint, features *cpuid.FeatureSet, filename string, passedFile *fs.File, argv []string) (loadedELF, arch.Context, *fs.Dirent, []string, error) {
|
||||
func loadBinary(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, remainingTraversals *uint, features *cpuid.FeatureSet, filename string, passedFile *fs.File, argv []string, resolveFinal bool) (loadedELF, arch.Context, *fs.Dirent, []string, error) {
|
||||
for i := 0; i < maxLoaderAttempts; i++ {
|
||||
var (
|
||||
d *fs.Dirent
|
||||
@@ -189,8 +198,7 @@ func loadBinary(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamesp
|
||||
err error
|
||||
)
|
||||
if passedFile == nil {
|
||||
d, f, err = openPath(ctx, mounts, root, wd, remainingTraversals, filename)
|
||||
|
||||
d, f, err = openPath(ctx, mounts, root, wd, remainingTraversals, filename, resolveFinal)
|
||||
} else {
|
||||
d, f, err = openFile(ctx, passedFile, nil, "")
|
||||
// Set to nil in case we loop on a Interpreter Script.
|
||||
@@ -255,9 +263,9 @@ func loadBinary(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamesp
|
||||
// Preconditions:
|
||||
// * The Task MemoryManager is empty.
|
||||
// * Load is called on the Task goroutine.
|
||||
func Load(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, fs *cpuid.FeatureSet, filename string, file *fs.File, argv, envv []string, extraAuxv []arch.AuxEntry, vdso *VDSO) (abi.OS, arch.Context, string, *syserr.Error) {
|
||||
func Load(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, fs *cpuid.FeatureSet, filename string, file *fs.File, argv, envv []string, resolveFinal bool, extraAuxv []arch.AuxEntry, vdso *VDSO) (abi.OS, arch.Context, string, *syserr.Error) {
|
||||
// Load the binary itself.
|
||||
loaded, ac, d, argv, err := loadBinary(ctx, m, mounts, root, wd, maxTraversals, fs, filename, file, argv)
|
||||
loaded, ac, d, argv, err := loadBinary(ctx, m, mounts, root, wd, maxTraversals, fs, filename, file, argv, resolveFinal)
|
||||
if err != nil {
|
||||
return 0, nil, "", syserr.NewDynamic(fmt.Sprintf("Failed to load %s: %v", filename, err), syserr.FromError(err).ToLinux())
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ var AMD64 = &kernel.SyscallTable{
|
||||
319: syscalls.Supported("memfd_create", MemfdCreate),
|
||||
320: syscalls.CapError("kexec_file_load", linux.CAP_SYS_BOOT, "", nil),
|
||||
321: syscalls.CapError("bpf", linux.CAP_SYS_ADMIN, "", nil),
|
||||
322: syscalls.PartiallySupported("execveat", Execveat, "No support for AT_SYMLINK_FOLLOW.", nil),
|
||||
322: syscalls.Supported("execveat", Execveat),
|
||||
323: syscalls.ErrorWithEvent("userfaultfd", syserror.ENOSYS, "", []string{"gvisor.dev/issue/266"}), // TODO(b/118906345)
|
||||
324: syscalls.ErrorWithEvent("membarrier", syserror.ENOSYS, "", []string{"gvisor.dev/issue/267"}), // TODO(b/118904897)
|
||||
325: syscalls.PartiallySupported("mlock2", Mlock2, "Stub implementation. The sandbox lacks appropriate permissions.", nil),
|
||||
|
||||
@@ -105,16 +105,14 @@ func execveat(t *kernel.Task, dirFD int32, pathnameAddr, argvAddr, envvAddr user
|
||||
}
|
||||
}
|
||||
|
||||
if flags&linux.AT_SYMLINK_NOFOLLOW != 0 {
|
||||
// TODO(b/128449944): Handle AT_SYMLINK_NOFOLLOW.
|
||||
t.Kernel().EmitUnimplementedEvent(t)
|
||||
return 0, nil, syserror.ENOSYS
|
||||
if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 {
|
||||
return 0, nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
atEmptyPath := flags&linux.AT_EMPTY_PATH != 0
|
||||
if !atEmptyPath && len(pathname) == 0 {
|
||||
return 0, nil, syserror.ENOENT
|
||||
}
|
||||
resolveFinal := flags&linux.AT_SYMLINK_NOFOLLOW == 0
|
||||
|
||||
root := t.FSContext().RootDirectory()
|
||||
defer root.DecRef()
|
||||
@@ -150,7 +148,7 @@ func execveat(t *kernel.Task, dirFD int32, pathnameAddr, argvAddr, envvAddr user
|
||||
|
||||
// Load the new TaskContext.
|
||||
maxTraversals := uint(linux.MaxSymlinkTraversals)
|
||||
tc, se := t.Kernel().LoadTaskImage(t, t.MountNamespace(), root, wd, &maxTraversals, pathname, executable, argv, envv, t.Arch().FeatureSet())
|
||||
tc, se := t.Kernel().LoadTaskImage(t, t.MountNamespace(), root, wd, &maxTraversals, pathname, executable, argv, envv, resolveFinal, t.Arch().FeatureSet())
|
||||
if se != nil {
|
||||
return 0, nil, se.ToError()
|
||||
}
|
||||
|
||||
@@ -542,23 +542,23 @@ TEST(ExecveatTest, BasicWithFDCWD) {
|
||||
TEST(ExecveatTest, Basic) {
|
||||
std::string absolute_path = WorkloadPath(kBasicWorkload);
|
||||
std::string parent_dir = std::string(Dirname(absolute_path));
|
||||
std::string relative_path = std::string(Basename(absolute_path));
|
||||
std::string base = std::string(Basename(absolute_path));
|
||||
const FileDescriptor dirfd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(parent_dir, O_DIRECTORY));
|
||||
|
||||
CheckExecveat(dirfd.get(), relative_path, {absolute_path}, {}, /*flags=*/0,
|
||||
CheckExecveat(dirfd.get(), base, {absolute_path}, {}, /*flags=*/0,
|
||||
ArgEnvExitStatus(0, 0), absl::StrCat(absolute_path, "\n"));
|
||||
}
|
||||
|
||||
TEST(ExecveatTest, FDNotADirectory) {
|
||||
std::string absolute_path = WorkloadPath(kBasicWorkload);
|
||||
std::string relative_path = std::string(Basename(absolute_path));
|
||||
std::string base = std::string(Basename(absolute_path));
|
||||
const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(absolute_path, 0));
|
||||
|
||||
int execve_errno;
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(fd.get(), relative_path,
|
||||
{absolute_path}, {}, /*flags=*/0,
|
||||
/*child=*/nullptr, &execve_errno));
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(fd.get(), base, {absolute_path}, {},
|
||||
/*flags=*/0, /*child=*/nullptr,
|
||||
&execve_errno));
|
||||
EXPECT_EQ(execve_errno, ENOTDIR);
|
||||
}
|
||||
|
||||
@@ -618,14 +618,77 @@ TEST(ExecveatTest, AbsolutePathWithEmptyPathFlag) {
|
||||
TEST(ExecveatTest, RelativePathWithEmptyPathFlag) {
|
||||
std::string absolute_path = WorkloadPath(kBasicWorkload);
|
||||
std::string parent_dir = std::string(Dirname(absolute_path));
|
||||
std::string relative_path = std::string(Basename(absolute_path));
|
||||
std::string base = std::string(Basename(absolute_path));
|
||||
const FileDescriptor dirfd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(parent_dir, O_DIRECTORY));
|
||||
|
||||
CheckExecveat(dirfd.get(), relative_path, {absolute_path}, {}, AT_EMPTY_PATH,
|
||||
CheckExecveat(dirfd.get(), base, {absolute_path}, {}, AT_EMPTY_PATH,
|
||||
ArgEnvExitStatus(0, 0), absl::StrCat(absolute_path, "\n"));
|
||||
}
|
||||
|
||||
TEST(ExecveatTest, SymlinkNoFollowWithRelativePath) {
|
||||
std::string parent_dir = "/tmp";
|
||||
TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
TempPath::CreateSymlinkTo(parent_dir, WorkloadPath(kBasicWorkload)));
|
||||
const FileDescriptor dirfd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(parent_dir, O_DIRECTORY));
|
||||
std::string base = std::string(Basename(link.path()));
|
||||
|
||||
int execve_errno;
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(dirfd.get(), base, {base}, {},
|
||||
AT_SYMLINK_NOFOLLOW,
|
||||
/*child=*/nullptr, &execve_errno));
|
||||
EXPECT_EQ(execve_errno, ELOOP);
|
||||
}
|
||||
|
||||
TEST(ExecveatTest, SymlinkNoFollowWithAbsolutePath) {
|
||||
std::string parent_dir = "/tmp";
|
||||
TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
TempPath::CreateSymlinkTo(parent_dir, WorkloadPath(kBasicWorkload)));
|
||||
std::string path = link.path();
|
||||
|
||||
int execve_errno;
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(AT_FDCWD, path, {path}, {},
|
||||
AT_SYMLINK_NOFOLLOW,
|
||||
/*child=*/nullptr, &execve_errno));
|
||||
EXPECT_EQ(execve_errno, ELOOP);
|
||||
}
|
||||
|
||||
TEST(ExecveatTest, SymlinkNoFollowAndEmptyPath) {
|
||||
TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
TempPath::CreateSymlinkTo("/tmp", WorkloadPath(kBasicWorkload)));
|
||||
std::string path = link.path();
|
||||
const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, 0));
|
||||
|
||||
CheckExecveat(fd.get(), "", {path}, {}, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
|
||||
ArgEnvExitStatus(0, 0), absl::StrCat(path, "\n"));
|
||||
}
|
||||
|
||||
TEST(ExecveatTest, SymlinkNoFollowIgnoreSymlinkAncestor) {
|
||||
TempPath parent_link =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateSymlinkTo("/tmp", "/bin"));
|
||||
std::string path_with_symlink = JoinPath(parent_link.path(), "echo");
|
||||
|
||||
CheckExecveat(AT_FDCWD, path_with_symlink, {path_with_symlink}, {},
|
||||
AT_SYMLINK_NOFOLLOW, ArgEnvExitStatus(0, 0), "");
|
||||
}
|
||||
|
||||
TEST(ExecveatTest, SymlinkNoFollowWithNormalFile) {
|
||||
const FileDescriptor dirfd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open("/bin", O_DIRECTORY));
|
||||
|
||||
CheckExecveat(dirfd.get(), "echo", {"echo"}, {}, AT_SYMLINK_NOFOLLOW,
|
||||
ArgEnvExitStatus(0, 0), "");
|
||||
}
|
||||
|
||||
TEST(ExecveatTest, InvalidFlags) {
|
||||
int execve_errno;
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(
|
||||
/*dirfd=*/-1, "", {}, {}, /*flags=*/0xFFFF, /*child=*/nullptr,
|
||||
&execve_errno));
|
||||
EXPECT_EQ(execve_errno, EINVAL);
|
||||
}
|
||||
|
||||
// Priority consistent across calls to execve()
|
||||
TEST(GetpriorityTest, ExecveMaintainsPriority) {
|
||||
int prio = 16;
|
||||
|
||||
Reference in New Issue
Block a user