master
cpp 217 lines 7.13 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2 #include <linux/unistd.h>
3
4 #include "SecCompDispatcher.h"
5 #include "common.h"
6 #include "Syscall.h"
7 #include "SyscallError.h"
8
9 static int seccomp(unsigned int operation, unsigned int flags, void* args, const std::source_location& source = std::source_location::current())
10 {
11 int result = syscall(__NR_seccomp, operation, flags, args);
12 if (result < 0)
13 {
14 auto error = errno;
15 std::stringstream argString;
16 detail::PrettyPrintArguments(argString, operation, flags, args);
17 throw SyscallError("seccomp", argString.str(), error, source);
18 }
19
20 return result;
21 }
22
23 SecCompDispatcher::SecCompDispatcher(int m_NotifyFd) : m_notifyFd(m_NotifyFd)
24 {
25 seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &m_notificationSizes);
26
27 m_worker = std::thread([this]() { Run(); });
28 }
29
30 SecCompDispatcher::~SecCompDispatcher()
31 {
32 m_shutdown.reset();
33 m_worker.join();
34 }
35
36 /**
37 * @brief Poll for notifications from seccomp and dispatch them a handler.
38 *
39 */
40 void SecCompDispatcher::Run()
41 {
42 UtilSetThreadName("SecCompDispatcher");
43
44 // Create a pipe to signal the thread to stop.
45 int pipes[2];
46 Syscall(pipe2, pipes, 0);
47 m_shutdown = pipes[1];
48 wil::unique_fd terminate = pipes[0];
49 auto wait_for_fd = [&terminate](int fd, int event) -> bool {
50 struct pollfd poll_fds[2];
51 poll_fds[0] = {.fd = fd, .events = event, .revents = 0};
52 poll_fds[1] = {.fd = terminate.get(), .events = POLLIN, .revents = 0};
53 for (;;)
54 {
55 int return_value = SyscallInterruptable(poll, poll_fds, ARRAY_SIZE(poll_fds), -1);
56 if (return_value < 0)
57 {
58 continue;
59 }
60 else if (return_value == 0)
61 {
62 continue;
63 }
64 else if (poll_fds[1].revents)
65 {
66 return false;
67 }
68 else if (poll_fds[0].revents & event)
69 {
70 return true;
71 }
72 }
73 };
74 std::vector<uint8_t> notification_buffer(m_notificationSizes.seccomp_notif);
75 std::vector<std::uint8_t> response_buffer(m_notificationSizes.seccomp_notif_resp);
76 assert(m_notificationSizes.seccomp_notif_resp >= sizeof(seccomp_notif_resp));
77 for (;;)
78 {
79 if (!wait_for_fd(m_notifyFd.get(), POLLIN))
80 {
81 break;
82 }
83
84 // Clear the buffers to make the 5.15 kernel happy.
85 notification_buffer.clear();
86 notification_buffer.resize(m_notificationSizes.seccomp_notif);
87
88 auto* callInfo = reinterpret_cast<seccomp_notif*>(notification_buffer.data());
89 try
90 {
91 Syscall(ioctl, m_notifyFd.get(), SECCOMP_IOCTL_NOTIF_RECV, callInfo);
92 }
93 catch (const SyscallError& e)
94 {
95 if (e.GetErrno() == ENOENT)
96 {
97 // The target thread was killed by a signal as the notification information was being generated,
98 // or the target's (blocked) system call was interrupted by a signal handler.
99 GNS_LOG_INFO("SECCOMP_IOCTL_NOTIF_RECV failed with ENOENT");
100 continue;
101 }
102
103 throw;
104 }
105 int result = 0;
106 GNS_LOG_INFO(
107 "Notified for arch {:X} syscall {} with id {} for pid {} with args ({:X}, {:X}, {:X}, {:X}, {:X}, "
108 "{:X})",
109 callInfo->data.arch,
110 callInfo->data.nr,
111 callInfo->id,
112 callInfo->pid,
113 callInfo->data.args[0],
114 callInfo->data.args[1],
115 callInfo->data.args[2],
116 callInfo->data.args[3],
117 callInfo->data.args[4],
118 callInfo->data.args[5]);
119
120 auto handler = m_handlers.find(callInfo->data.nr);
121
122 try
123 {
124 if (handler != m_handlers.end())
125 {
126 result = handler->second(callInfo);
127 }
128 }
129 catch (std::exception& e)
130 {
131 GNS_LOG_ERROR("Dispatch of call failed, {}", e.what());
132 }
133
134 response_buffer.clear();
135 response_buffer.resize(m_notificationSizes.seccomp_notif_resp);
136
137 auto* resultInfo = reinterpret_cast<seccomp_notif_resp*>(response_buffer.data());
138 resultInfo->id = callInfo->id;
139 resultInfo->error = -result;
140 resultInfo->val = 0;
141 resultInfo->flags = result == 0 ? SECCOMP_USER_NOTIF_FLAG_CONTINUE : 0;
142
143 GNS_LOG_INFO("Responding to notification with id {} for pid {}, result {}", callInfo->id, callInfo->pid, result);
144 try
145 {
146 Syscall(ioctl, m_notifyFd.get(), SECCOMP_IOCTL_NOTIF_SEND, resultInfo);
147 }
148 catch (std::exception& e)
149 {
150 GNS_LOG_ERROR("Failed to respond to notification with id {} for pid {}, {}", callInfo->id, callInfo->pid, e.what());
151 }
152 }
153 }
154
155 bool SecCompDispatcher::ValidateCookie(uint64_t id) noexcept
156 {
157 try
158 {
159 // If the cookie is not valid, the ioctl will return < 0 and the call below will throw
160 Syscall(ioctl, m_notifyFd.get(), SECCOMP_IOCTL_NOTIF_ID_VALID, &id);
161 return true;
162 }
163 catch (std::exception& e)
164 {
165 return false;
166 }
167 }
168
169 void SecCompDispatcher::RegisterHandler(int SysCallNr, const std::function<int(seccomp_notif*)>& Handler)
170 {
171 m_handlers[SysCallNr] = Handler;
172 }
173
174 void SecCompDispatcher::UnregisterHandler(int SysCallNr)
175 {
176 m_handlers.erase(SysCallNr);
177 }
178
179 std::optional<std::vector<gsl::byte>> SecCompDispatcher::ReadProcessMemory(uint64_t Cookie, pid_t Pid, size_t Address, size_t Length) noexcept
180 {
181 try
182 {
183 std::vector<gsl::byte> targetMemory(Length);
184 const std::string path = std::format("/proc/{}/mem", Pid);
185 wil::unique_fd mem(Syscall(open, path.c_str(), O_RDWR));
186
187 // PID reuse can cause a TOCTOU race here, so validate that the notification is still
188 // valid to make sure that the above fd points to the right process
189 if (!ValidateCookie(Cookie))
190 {
191 throw RuntimeErrorWithSourceLocation(std::format("Invalid cookie {}", Cookie));
192 }
193
194 Syscall(lseek64, mem.get(), Address, SEEK_SET);
195 if (Syscall(read, mem.get(), targetMemory.data(), targetMemory.size()) != targetMemory.size())
196 {
197 throw RuntimeErrorWithSourceLocation(std::format("Couldn't read the whole call address with error {}", errno));
198 }
199
200 // Based on https://man7.org/linux/man-pages/man2/seccomp_unotify.2.html (example in function getTargetPathname),
201 // it's possible that right before reading the process memory, the intercepted system call was interrupted by a signal and
202 // the memory we read may no longer be associated with that system call
203 //
204 // We need to validate the cookie again to make sure the seccomp notification is still valid after we read the process memory
205 if (!ValidateCookie(Cookie))
206 {
207 throw RuntimeErrorWithSourceLocation(std::format("Invalid cookie {}", Cookie));
208 }
209
210 return targetMemory;
211 }
212 catch (std::exception& e)
213 {
214 GNS_LOG_ERROR("Failed to read process memory for pid {}, cookie {}, {}", Pid, Cookie, e.what());
215 return std::nullopt;
216 }
217 }