Skip to content

Commit b590d2b

Browse files
AnilAltinaygvisor-bot
authored andcommitted
Fix leaking /proc/self/mem of a new process after execve.
memFD is linked with the Task instead of MemoryManager. At the time of read/write, it uses Task's MemoryManager. However, it should have used Task's MemoryManager from the time when we do open. To fix it, we store the MemoryManager at the time of opening /proc/self/mem. Added the reproducer as a test. PiperOrigin-RevId: 760855949
1 parent b47d21e commit b590d2b

File tree

3 files changed

+109
-8
lines changed

3 files changed

+109
-8
lines changed

pkg/sentry/fsimpl/proc/task_files.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,9 @@ func (f *memInode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.De
453453
if err := checkTaskState(f.task); err != nil {
454454
return nil, err
455455
}
456-
fd := &memFD{}
456+
fd := &memFD{
457+
mm: getMM(f.task),
458+
}
457459
if err := fd.Init(rp.Mount(), d, f, opts.Flags); err != nil {
458460
return nil, err
459461
}
@@ -476,7 +478,7 @@ type memFD struct {
476478
vfs.LockFD
477479

478480
inode *memInode
479-
481+
mm *mm.MemoryManager
480482
// mu guards the fields below.
481483
mu sync.Mutex `state:"nosave"`
482484
offset int64
@@ -515,9 +517,9 @@ func (fd *memFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int6
515517
if src.NumBytes() == 0 {
516518
return 0, nil
517519
}
518-
m, err := getMMIncRef(fd.inode.task)
519-
if err != nil {
520-
return 0, err
520+
m := fd.mm
521+
if m == nil || !m.IncUsers() {
522+
return 0, nil
521523
}
522524
defer m.DecUsers(ctx)
523525
// Buffer the write data because of MM locks
@@ -542,9 +544,9 @@ func (fd *memFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64
542544
if dst.NumBytes() == 0 {
543545
return 0, nil
544546
}
545-
m, err := getMMIncRef(fd.inode.task)
546-
if err != nil {
547-
return 0, err
547+
m := fd.mm
548+
if m == nil || !m.IncUsers() {
549+
return 0, nil
548550
}
549551
defer m.DecUsers(ctx)
550552
// Buffer the read data because of MM locks

test/syscalls/linux/BUILD

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,13 +767,18 @@ cc_binary(
767767
deps = select_gtest() + [
768768
"//test/util:file_descriptor",
769769
"//test/util:fs_util",
770+
"//test/util:logging",
771+
"//test/util:memory_util",
770772
"//test/util:multiprocess_util",
771773
"//test/util:posix_error",
774+
"//test/util:save_util",
772775
"//test/util:temp_path",
773776
"//test/util:test_util",
774777
"//test/util:thread_util",
775778
"@com_google_absl//absl/strings",
779+
"@com_google_absl//absl/strings:str_format",
776780
"@com_google_absl//absl/synchronization",
781+
"@com_google_absl//absl/time",
777782
"@com_google_absl//absl/types:optional",
778783
],
779784
)

test/syscalls/linux/exec.cc

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <errno.h>
1818
#include <fcntl.h>
1919
#include <sys/eventfd.h>
20+
#include <sys/mman.h>
2021
#include <sys/ptrace.h>
2122
#include <sys/resource.h>
2223
#include <sys/time.h>
@@ -33,15 +34,20 @@
3334
#include "gmock/gmock.h"
3435
#include "gtest/gtest.h"
3536
#include "absl/strings/match.h"
37+
#include "absl/strings/numbers.h"
3638
#include "absl/strings/str_cat.h"
39+
#include "absl/strings/str_format.h"
3740
#include "absl/strings/str_split.h"
3841
#include "absl/strings/string_view.h"
3942
#include "absl/synchronization/mutex.h"
4043
#include "absl/types/optional.h"
4144
#include "test/util/file_descriptor.h"
4245
#include "test/util/fs_util.h"
46+
#include "test/util/logging.h"
47+
#include "test/util/memory_util.h"
4348
#include "test/util/multiprocess_util.h"
4449
#include "test/util/posix_error.h"
50+
#include "test/util/save_util.h"
4551
#include "test/util/temp_path.h"
4652
#include "test/util/test_util.h"
4753
#include "test/util/thread_util.h"
@@ -63,6 +69,9 @@ constexpr char kPriorityWorkload[] = "test/syscalls/linux/priority_execve";
6369
constexpr char kExit42[] = "--exec_exit_42";
6470
constexpr char kExecWithThread[] = "--exec_exec_with_thread";
6571
constexpr char kExecFromThread[] = "--exec_exec_from_thread";
72+
constexpr char kExecInParent[] = "--exec_exec_in_parent";
73+
constexpr char kWriteAndWaitForPid[] = "--exec_write_and_wait_for_pid";
74+
const unsigned int mmapAddr = 0xD0000000;
6675

6776
// Runs file specified by dirfd and pathname with argv and checks that the exit
6877
// status is expect_status and that stderr contains expect_stderr.
@@ -851,6 +860,75 @@ TEST(ExecTest, Setpgid) {
851860
EXPECT_THAT(setpgid(getpid(), pid), SyscallSucceeds());
852861
}
853862

863+
TEST(ExecTest, ReadProcMemAfterExecFromChild) {
864+
// Fork and exec in the parent process.
865+
CheckExec("/proc/self/exe", {"/proc/self/exe", kExecInParent}, {},
866+
W_EXITCODE(42, 0), "");
867+
}
868+
869+
void execInParent() {
870+
auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
871+
int pipe_fd[2] = {};
872+
ASSERT_THAT(pipe(pipe_fd), SyscallSucceeds());
873+
874+
const auto readSecret = [&] {
875+
// Close writing end of the pipe.
876+
close(pipe_fd[1]);
877+
// Await parent OK to read the secret.
878+
char ok = 0;
879+
TEST_CHECK(ReadFd(pipe_fd[0], &ok, sizeof(ok)) == sizeof(ok));
880+
// Close the reading end of the pipe.
881+
TEST_PCHECK(close(pipe_fd[0]) == 0);
882+
// Parent sent the signal. Read the secret.
883+
// Use /proc/mem fd from parent to try to read the secret.
884+
char secret[] = "secret";
885+
char output[sizeof(secret)];
886+
TEST_PCHECK_MSG(
887+
pread(memfd.get(), output, sizeof(output), mmapAddr) != sizeof(secret),
888+
"pread succeeded. It should have failed.");
889+
};
890+
pid_t pid = fork();
891+
// In child process.
892+
if (pid == 0) {
893+
readSecret();
894+
TEST_CHECK_MSG(!::testing::Test::HasFailure(),
895+
"EXPECT*/ASSERT* failed. These are not async-signal-safe "
896+
"and must not be called from fn.");
897+
_exit(0);
898+
}
899+
// In parent process.
900+
MaybeSave();
901+
ASSERT_THAT(pid, SyscallSucceeds());
902+
903+
// Execve with args{function name, child_pid, pipe_fd[1]}
904+
const ExecveArray argv = {"/proc/self/exe", kWriteAndWaitForPid,
905+
absl::StrCat(pid) /*child_pid*/,
906+
absl::StrCat(pipe_fd[1])};
907+
const ExecveArray envv;
908+
execve("/proc/self/exe", argv.get(), envv.get());
909+
}
910+
911+
void writeAndWaitForPid(int child_pid, int pipe_fd) {
912+
// mmap the same address that the child process will try to read.
913+
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
914+
Mmap(reinterpret_cast<void*>(mmapAddr), kPageSize, PROT_READ | PROT_WRITE,
915+
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0));
916+
const char secret[] = "secret";
917+
absl::SNPrintF((char*)m.addr(), sizeof(secret), "%s", secret);
918+
919+
// Tell the child process to read the secret.
920+
char ok = 1;
921+
EXPECT_THAT(WriteFd(pipe_fd, &ok, sizeof(ok)),
922+
SyscallSucceedsWithValue(sizeof(ok)));
923+
EXPECT_THAT(close(pipe_fd), SyscallSucceeds());
924+
925+
// Wait for child process to read the exit.
926+
int status;
927+
ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
928+
ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
929+
exit(42);
930+
}
931+
854932
void ExecWithThread() {
855933
// Used to ensure that the thread has actually started.
856934
absl::Mutex mu;
@@ -948,6 +1026,22 @@ int main(int argc, char** argv) {
9481026
gvisor::testing::ExecFromThread();
9491027
return 1;
9501028
}
1029+
if (arg == gvisor::testing::kExecInParent) {
1030+
gvisor::testing::execInParent();
1031+
return 1;
1032+
}
1033+
if (arg == gvisor::testing::kWriteAndWaitForPid) {
1034+
int pid;
1035+
if (!absl::SimpleAtoi(argv[i + 1], &pid)) {
1036+
return 1;
1037+
}
1038+
int fd;
1039+
if (!absl::SimpleAtoi(argv[i + 2], &fd)) {
1040+
return 1;
1041+
}
1042+
gvisor::testing::writeAndWaitForPid(pid, fd);
1043+
return 1;
1044+
}
9511045
}
9521046

9531047
gvisor::testing::TestInit(&argc, &argv);

0 commit comments

Comments
 (0)