summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNicolas Lacasse <nlacasse@google.com>2019-06-26 11:48:09 -0700
committergVisor bot <gvisor-bot@google.com>2019-06-26 11:49:20 -0700
commit857e5c47e914aeeec12662d85466d91bf4ce3504 (patch)
treec0c4620f4579d13dedd876bfeaf876b384b03a09
parent67e2f227aac49129936efc640a6c47a0978b187d (diff)
Follow symlinks when creating a file, and create the target.
If we have a symlink whose target does not exist, creating the symlink (either via 'creat' or 'open' with O_CREAT flag) should create the target of the symlink. Previously, gVisor would error with EEXIST in this case PiperOrigin-RevId: 255232944
-rw-r--r--pkg/sentry/syscalls/linux/sys_file.go106
-rw-r--r--test/syscalls/linux/symlink.cc71
2 files changed, 152 insertions, 25 deletions
diff --git a/pkg/sentry/syscalls/linux/sys_file.go b/pkg/sentry/syscalls/linux/sys_file.go
index d9ed02c99..04962726a 100644
--- a/pkg/sentry/syscalls/linux/sys_file.go
+++ b/pkg/sentry/syscalls/linux/sys_file.go
@@ -304,44 +304,100 @@ func createAt(t *kernel.Task, dirFD kdefs.FD, addr usermem.Addr, flags uint, mod
return 0, syserror.ENOENT
}
- err = fileOpAt(t, dirFD, path, func(root *fs.Dirent, d *fs.Dirent, name string, remainingTraversals uint) error {
- if !fs.IsDir(d.Inode.StableAttr) {
- return syserror.ENOTDIR
- }
+ fileFlags := linuxToFlags(flags)
+ // Linux always adds the O_LARGEFILE flag when running in 64-bit mode.
+ fileFlags.LargeFile = true
+
+ err = fileOpAt(t, dirFD, path, func(root *fs.Dirent, parent *fs.Dirent, name string, remainingTraversals uint) error {
+ // Resolve the name to see if it exists, and follow any
+ // symlinks along the way. We must do the symlink resolution
+ // manually because if the symlink target does not exist, we
+ // must create the target (and not the symlink itself).
+ var (
+ found *fs.Dirent
+ err error
+ )
+ for {
+ if !fs.IsDir(parent.Inode.StableAttr) {
+ return syserror.ENOTDIR
+ }
- fileFlags := linuxToFlags(flags)
- // Linux always adds the O_LARGEFILE flag when running in 64-bit mode.
- fileFlags.LargeFile = true
+ // Start by looking up the dirent at 'name'.
+ found, err = t.MountNamespace().FindLink(t, root, parent, name, &remainingTraversals)
+ if err != nil {
+ break
+ }
+
+ // We found something (possibly a symlink). If the
+ // O_EXCL flag was passed, then we can immediately
+ // return EEXIST.
+ if flags&linux.O_EXCL != 0 {
+ return syserror.EEXIST
+ }
+
+ // If we have a non-symlink, then we can proceed.
+ if !fs.IsSymlink(found.Inode.StableAttr) {
+ break
+ }
+
+ // If O_NOFOLLOW was passed, then don't try to resolve
+ // anything.
+ if flags&linux.O_NOFOLLOW != 0 {
+ return syserror.ELOOP
+ }
+
+ // Try to resolve the symlink directly to a Dirent.
+ resolved, err := found.Inode.Getlink(t)
+ if err == nil || err != fs.ErrResolveViaReadlink {
+ // No more resolution necessary.
+ found.DecRef()
+ found = resolved
+ break
+ }
+
+ // Resolve the symlink to a path via Readlink.
+ path, err := found.Inode.Readlink(t)
+ if err != nil {
+ break
+ }
+ remainingTraversals--
+
+ // Get the new parent from the target path.
+ newParentPath, newName := fs.SplitLast(path)
+ newParent, err := t.MountNamespace().FindInode(t, root, parent, newParentPath, &remainingTraversals)
+ if err != nil {
+ break
+ }
+
+ // Repeat the process with the parent and name of the
+ // symlink target.
+ parent.DecRef()
+ parent = newParent
+ name = newName
+ }
- // Does this file exist already?
- targetDirent, err := t.MountNamespace().FindInode(t, root, d, name, &remainingTraversals)
var newFile *fs.File
switch err {
case nil:
// The file existed.
- defer targetDirent.DecRef()
-
- // Check if we wanted to create.
- if flags&linux.O_EXCL != 0 {
- return syserror.EEXIST
- }
+ defer found.DecRef()
// Like sys_open, check for a few things about the
// filesystem before trying to get a reference to the
// fs.File. The same constraints on Check apply.
- if err := targetDirent.Inode.CheckPermission(t, flagsToPermissions(flags)); err != nil {
+ if err := found.Inode.CheckPermission(t, flagsToPermissions(flags)); err != nil {
return err
}
// Should we truncate the file?
if flags&linux.O_TRUNC != 0 {
- if err := targetDirent.Inode.Truncate(t, targetDirent, 0); err != nil {
+ if err := found.Inode.Truncate(t, found, 0); err != nil {
return err
}
}
// Create a new fs.File.
- newFile, err = targetDirent.Inode.GetFile(t, targetDirent, fileFlags)
+ newFile, err = found.Inode.GetFile(t, found, fileFlags)
if err != nil {
return syserror.ConvertIntr(err, kernel.ERESTARTSYS)
}
@@ -350,19 +406,19 @@ func createAt(t *kernel.Task, dirFD kdefs.FD, addr usermem.Addr, flags uint, mod
// File does not exist. Proceed with creation.
// Do we have write permissions on the parent?
- if err := d.Inode.CheckPermission(t, fs.PermMask{Write: true, Execute: true}); err != nil {
+ if err := parent.Inode.CheckPermission(t, fs.PermMask{Write: true, Execute: true}); err != nil {
return err
}
// Attempt a creation.
perms := fs.FilePermsFromMode(mode &^ linux.FileMode(t.FSContext().Umask()))
- newFile, err = d.Create(t, root, name, fileFlags, perms)
+ newFile, err = parent.Create(t, root, name, fileFlags, perms)
if err != nil {
// No luck, bail.
return err
}
defer newFile.DecRef()
- targetDirent = newFile.Dirent
+ found = newFile.Dirent
default:
return err
}
@@ -378,10 +434,10 @@ func createAt(t *kernel.Task, dirFD kdefs.FD, addr usermem.Addr, flags uint, mod
fd = uintptr(newFD)
// Queue the open inotify event. The creation event is
- // automatically queued when the dirent is targetDirent. The
- // open events are implemented at the syscall layer so we need
- // to manually queue one here.
- targetDirent.InotifyEvent(linux.IN_OPEN, 0)
+ // automatically queued when the dirent is found. The open
+ // events are implemented at the syscall layer so we need to
+ // manually queue one here.
+ found.InotifyEvent(linux.IN_OPEN, 0)
return nil
})
diff --git a/test/syscalls/linux/symlink.cc b/test/syscalls/linux/symlink.cc
index 494072a9b..dce8de9ec 100644
--- a/test/syscalls/linux/symlink.cc
+++ b/test/syscalls/linux/symlink.cc
@@ -272,6 +272,77 @@ TEST(SymlinkTest, ChmodSymlink) {
EXPECT_EQ(FilePermission(newpath), 0777);
}
+class ParamSymlinkTest : public ::testing::TestWithParam<std::string> {};
+
+// Test that creating an existing symlink with creat will create the target.
+TEST_P(ParamSymlinkTest, CreatLinkCreatesTarget) {
+ const std::string target = GetParam();
+ const std::string linkpath = NewTempAbsPath();
+
+ ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds());
+
+ int fd;
+ EXPECT_THAT(fd = creat(linkpath.c_str(), 0666), SyscallSucceeds());
+ ASSERT_THAT(close(fd), SyscallSucceeds());
+
+ ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
+ struct stat st;
+ EXPECT_THAT(stat(target.c_str(), &st), SyscallSucceeds());
+
+ ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
+ ASSERT_THAT(unlink(target.c_str()), SyscallSucceeds());
+}
+
+// Test that opening an existing symlink with O_CREAT will create the target.
+TEST_P(ParamSymlinkTest, OpenLinkCreatesTarget) {
+ const std::string target = GetParam();
+ const std::string linkpath = NewTempAbsPath();
+
+ ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds());
+
+ int fd;
+ EXPECT_THAT(fd = open(linkpath.c_str(), O_CREAT, 0666), SyscallSucceeds());
+ ASSERT_THAT(close(fd), SyscallSucceeds());
+
+ ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
+ struct stat st;
+ EXPECT_THAT(stat(target.c_str(), &st), SyscallSucceeds());
+
+ ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
+ ASSERT_THAT(unlink(target.c_str()), SyscallSucceeds());
+}
+
+// Test that opening an existing symlink with O_CREAT|O_EXCL will fail with
+// EEXIST.
+TEST_P(ParamSymlinkTest, OpenLinkExclFails) {
+ const std::string target = GetParam();
+ const std::string linkpath = NewTempAbsPath();
+
+ ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds());
+
+ EXPECT_THAT(open(linkpath.c_str(), O_CREAT | O_EXCL, 0666),
+ SyscallFailsWithErrno(EEXIST));
+
+ ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
+}
+
+// Test that opening an existing symlink with O_CREAT|O_NOFOLLOW will fail with
+// ELOOP.
+TEST_P(ParamSymlinkTest, OpenLinkNoFollowFails) {
+ const std::string target = GetParam();
+ const std::string linkpath = NewTempAbsPath();
+
+ ASSERT_THAT(symlink(target.c_str(), linkpath.c_str()), SyscallSucceeds());
+
+ EXPECT_THAT(open(linkpath.c_str(), O_CREAT | O_NOFOLLOW, 0666),
+ SyscallFailsWithErrno(ELOOP));
+
+ ASSERT_THAT(unlink(linkpath.c_str()), SyscallSucceeds());
+}
+
+INSTANTIATE_TEST_SUITE_P(AbsAndRelTarget, ParamSymlinkTest,
+ ::testing::Values(NewTempAbsPath(), NewTempRelPath()));
+
} // namespace
} // namespace testing