The Pedigree Project 0.1
user-namespace.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/process/TerminationDeferral.h"
4#include "pedigree/kernel/process/Thread.h"
5#include "pedigree/kernel/processor/Processor.h"
6#include "pedigree/kernel/processor/ProcessorInformation.h"
7#include "pedigree/kernel/syscallError.h"
8#include "pedigree/kernel/utilities/Pointers.h"
9#include "pedigree/kernel/utilities/StaticString.h"
10
11#include "PosixProcess.h"
12#include "sandbox-state.h"
13#include "user-namespace.h"
14
15namespace {
16uint64_t nextIdentity = 0xefffffff;
17Thread& current() {
18 return *Processor::information().getCurrentThread();
19}
20uint32_t effectiveUid(Thread& task) {
21 return static_cast<uint32_t>(task.getParent()->getEffectiveUserId());
22}
23bool number(const char*& cursor, const char* end, uint32_t& value) {
24 while (cursor != end && (*cursor == ' ' || *cursor == '\t')) {
25 ++cursor;
26 }
27 if (cursor == end || *cursor < '0' || *cursor > '9') {
28 return false;
29 }
30 uint64_t result = 0;
31 do {
32 result = result * 10 + (*cursor++ - '0');
33 if (result > UINT32_MAX) {
34 return false;
35 }
36 } while (cursor != end && *cursor >= '0' && *cursor <= '9');
37 value = static_cast<uint32_t>(result);
38 return true;
39}
40bool overlaps(uint32_t first, uint32_t count, uint32_t other, uint32_t otherCount) {
41 return uint64_t(first) < uint64_t(other) + otherCount &&
42 uint64_t(other) < uint64_t(first) + count;
43}
44int denied() {
45 SYSCALL_ERROR(NotEnoughPermissions);
46 return -1;
47}
48int invalid() {
49 SYSCALL_ERROR(InvalidArgument);
50 return -1;
51}
52} // namespace
53
54PosixUserNamespace::PosixUserNamespace(const UserNamespaceRef& parent, uint32_t owner,
55 uint32_t group, bool creatorSetfcap)
56 : m_Parent(parent),
57 m_Owner(owner),
58 m_Group(group),
59 m_Depth(parent ? parent->depth() + 1 : 1),
60 m_Identity(__atomic_add_fetch(&nextIdentity, 1, __ATOMIC_RELAXED)),
61 m_CreatorSetfcap(creatorSetfcap),
62 m_GroupsAllowed(!parent || parent->groupsAllowed()) {}
63
64bool PosixUserNamespace::toGlobal(bool group, uint32_t id, uint32_t& global, uint32_t count) const {
65 LockGuard<Mutex> guard(m_Lock);
66 const Map& map = group ? m_Gids : m_Uids;
67 for (size_t i = 0; i < map.count; ++i) {
68 const auto& range = map.ranges[i];
69 if (id >= range.inside && uint64_t(id) + count <= uint64_t(range.inside) + range.count) {
70 global = range.global + (id - range.inside);
71 return true;
72 }
73 }
74 return false;
75}
76
77bool PosixUserNamespace::fromGlobal(bool group, uint32_t global, uint32_t& id) const {
78 LockGuard<Mutex> guard(m_Lock);
79 const Map& map = group ? m_Gids : m_Uids;
80 for (size_t i = 0; i < map.count; ++i) {
81 const auto& range = map.ranges[i];
82 if (global >= range.global && uint64_t(global) < uint64_t(range.global) + range.count) {
83 id = range.inside + (global - range.global);
84 return true;
85 }
86 }
87 return false;
88}
89
90bool PosixUserNamespace::groupsAllowed() const {
91 LockGuard<Mutex> guard(m_Lock);
92 return m_GroupsAllowed;
93}
94
95int PosixUserNamespace::writeMap(bool group, const char* bytes, size_t length) {
96 TerminationDeferral lifetime;
97 if (!length || length >= 4096) {
98 return invalid();
99 }
100 const auto authority = posix_task_credentials(current());
101 if (authority.userNamespace.get() != this && authority.userNamespace != m_Parent) {
102 return denied();
103 }
104 const unsigned capability = group ? PosixCapabilities::Setgid : PosixCapabilities::Setuid;
105 // A creator in the parent has authority over the child, but never vice versa.
106 if (authority.userNamespace.get() == this) {
107 if (!(authority.effective & (uint64_t(1) << capability))) {
108 return denied();
109 }
110 } else if (effectiveUid(current()) != m_Owner && !posix_namespace_capable(m_Parent, capability)) {
111 return denied();
112 }
113
114 auto storage = UniquePointer<Map>::allocate();
115 Map* parsed = storage.get();
116 if (!parsed) {
117 SYSCALL_ERROR(OutOfMemory);
118 return -1;
119 }
120 const char* cursor = bytes;
121 const char* end = bytes + length;
122 while (cursor != end) {
123 if (parsed->count == MaximumRanges) {
124 return invalid();
125 }
126 auto& range = parsed->ranges[parsed->count];
127 if (!number(cursor, end, range.inside) || cursor == end ||
128 (*cursor != ' ' && *cursor != '\t') || !number(cursor, end, range.outside) ||
129 cursor == end || (*cursor != ' ' && *cursor != '\t') || !number(cursor, end, range.count) ||
130 !range.count || uint64_t(range.inside) + range.count > UINT32_MAX ||
131 uint64_t(range.outside) + range.count > UINT32_MAX) {
132 return invalid();
133 }
134 while (cursor != end && (*cursor == ' ' || *cursor == '\t')) {
135 ++cursor;
136 }
137 if (cursor != end && *cursor++ != '\n') {
138 return invalid();
139 }
140 for (size_t i = 0; i < parsed->count; ++i) {
141 const auto& previous = parsed->ranges[i];
142 if (overlaps(range.inside, range.count, previous.inside, previous.count) ||
143 overlaps(range.outside, range.count, previous.outside, previous.count)) {
144 return invalid();
145 }
146 }
147 range.global = range.outside;
148 if (m_Parent && !m_Parent->toGlobal(group, range.outside, range.global, range.count)) {
149 return denied();
150 }
151 if (!group && range.outside == 0 &&
152 !(authority.userNamespace.get() == this
153 ? m_CreatorSetfcap
154 : posix_namespace_capable(m_Parent, PosixCapabilities::Setfcap))) {
155 return denied();
156 }
157 ++parsed->count;
158 }
159 const bool parentPrivilege = posix_namespace_capable(m_Parent, capability);
160 const uint32_t real =
161 group ? current().getParent()->getGroupId() : current().getParent()->getUserId();
162 LockGuard<Mutex> guard(m_Lock);
163 Map& destination = group ? m_Gids : m_Uids;
164 if (destination.count) {
165 return denied();
166 }
167 if (!parentPrivilege &&
168 (parsed->count != 1 || parsed->ranges[0].count != 1 || parsed->ranges[0].global != real ||
169 effectiveUid(current()) != m_Owner || (group && m_GroupsAllowed))) {
170 return denied();
171 }
172 destination = *parsed;
173 current().setErrno(0);
174 return static_cast<int>(length);
175}
176
177size_t PosixUserNamespace::readMap(bool group, const UserNamespaceRef& viewer, char* bytes,
178 size_t capacity) const {
179 auto storage = UniquePointer<Map>::allocate();
180 Map* snapshot = storage.get();
181 if (!snapshot) {
182 SYSCALL_ERROR(OutOfMemory);
183 return 0;
184 }
185 {
186 LockGuard<Mutex> guard(m_Lock);
187 *snapshot = group ? m_Gids : m_Uids;
188 }
189 size_t used = 0;
190 for (size_t i = 0; i < snapshot->count; ++i) {
191 const auto& range = snapshot->ranges[i];
192 uint32_t outside = range.global;
193 if (viewer.get() == this) {
194 outside = range.outside;
195 } else if (viewer && !viewer->fromGlobal(group, range.global, outside)) {
196 outside = 65534;
197 }
199 line.append(range.inside);
200 line.append(' ');
201 line.append(outside);
202 line.append(' ');
203 line.append(range.count);
204 line.append('\n');
205 const size_t copied = line.length() < capacity - used ? line.length() : capacity - used;
206 MemoryCopy(bytes + used, static_cast<const char*>(line), copied);
207 used += copied;
208 if (used == capacity) {
209 break;
210 }
211 }
212 return used;
213}
214
215int PosixUserNamespace::writeSetgroups(const char* bytes, size_t length) {
216 TerminationDeferral lifetime;
217 bool allow;
218 if ((length == 4 || (length == 5 && bytes[4] == '\n')) && !MemoryCompare(bytes, "deny", 4)) {
219 allow = false;
220 } else if ((length == 5 || (length == 6 && bytes[5] == '\n')) &&
221 !MemoryCompare(bytes, "allow", 5)) {
222 allow = true;
223 } else {
224 return invalid();
225 }
226 const auto authority = posix_task_credentials(current());
227 if (authority.userNamespace.get() != this && authority.userNamespace != m_Parent) {
228 return denied();
229 }
230 if (authority.userNamespace.get() == this) {
231 if (!(authority.effective & (uint64_t(1) << PosixCapabilities::SysAdmin))) {
232 return denied();
233 }
234 } else if (effectiveUid(current()) != m_Owner &&
235 !posix_namespace_capable(m_Parent, PosixCapabilities::SysAdmin)) {
236 return denied();
237 }
238 LockGuard<Mutex> guard(m_Lock);
239 if (m_Gids.count || (allow && !m_GroupsAllowed)) {
240 return denied();
241 }
242 m_GroupsAllowed = allow;
243 current().setErrno(0);
244 return static_cast<int>(length);
245}
246
247PosixTaskCredentials posix_task_credentials(Thread& task) {
248 auto stored = posix_sandbox_credentials(task);
249 if (stored) {
250 return *stored;
251 }
253 if (!effectiveUid(task)) {
254 result.permitted = result.effective = PosixCapabilities::All;
255 }
256 return result;
257}
258UserNamespaceRef posix_user_namespace(Thread& task) {
259 auto stored = posix_sandbox_credentials(task);
260 return stored ? stored->userNamespace : UserNamespaceRef();
261}
262bool posix_namespace_capable(Thread& task, const UserNamespaceRef& target, unsigned capability) {
263 if (capability > PosixCapabilities::Last) {
264 return false;
265 }
266 const auto authority = posix_task_credentials(task);
267 auto space = target;
268 for (;;) {
269 if (space == authority.userNamespace) {
270 return authority.effective & (uint64_t(1) << capability);
271 }
272 if (!space) {
273 return false;
274 }
275 if (space->parent() == authority.userNamespace && space->owner() == effectiveUid(task)) {
276 return true;
277 }
278 space = space->parent();
279 }
280}
281bool posix_namespace_capable(const UserNamespaceRef& space, unsigned capability) {
282 return posix_namespace_capable(current(), space, capability);
283}
284bool posix_capable(unsigned capability) {
285 return posix_namespace_capable(posix_user_namespace(current()), capability);
286}
287bool posix_global_capable(unsigned capability) {
288 return posix_namespace_capable(UserNamespaceRef(), capability);
289}
290bool posix_user_namespace_prepare(Thread& creator, TaskCredentialsRef& result) {
291 auto parent = posix_user_namespace(creator);
292 if (parent && parent->depth() >= 32) {
293 SYSCALL_ERROR(NoSpaceLeftOnDevice);
294 return false;
295 }
296 uint32_t uid = effectiveUid(creator);
297 uint32_t gid = creator.getParent()->getEffectiveGroupId();
298 uint32_t ignored;
299 if (parent &&
300 (!parent->fromGlobal(false, uid, ignored) || !parent->fromGlobal(true, gid, ignored))) {
301 SYSCALL_ERROR(NotEnoughPermissions);
302 return false;
303 }
305 parent, uid, gid, posix_namespace_capable(creator, parent, PosixCapabilities::Setfcap));
306 auto replacement = TaskCredentialsRef::tryAllocate();
307 if (!space || !replacement) {
308 SYSCALL_ERROR(OutOfMemory);
309 return false;
310 }
311 replacement->userNamespace = space;
312 replacement->effective = replacement->permitted = PosixCapabilities::All;
313 result = replacement;
314 return true;
315}
316uint32_t posix_visible_id(bool group, uint32_t global) {
317 auto space = posix_user_namespace(current());
318 uint32_t visible = global;
319 if (space && !space->fromGlobal(group, global, visible)) {
320 return 65534;
321 }
322 return visible;
323}
324bool posix_global_id(bool group, uint32_t visible, uint32_t& global) {
325 if (visible == UINT32_MAX) {
326 global = visible;
327 return true;
328 }
329 auto space = posix_user_namespace(current());
330 global = visible;
331 return !space || space->toGlobal(group, visible, global);
332}
static ProcessorInformation & information()
static SharedPointer< PosixUserNamespace > tryAllocate(Args...)
T * get() const
Process * getParent() const
Definition Thread.h:340