The Pedigree Project 0.1
capability-state.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/process/TerminationDeferral.h"
3#include "pedigree/kernel/process/Thread.h"
4#include "pedigree/kernel/processor/Processor.h"
5#include "pedigree/kernel/processor/ProcessorInformation.h"
6#include "pedigree/kernel/syscallError.h"
7
8#include "sandbox-state.h"
9#include "user-namespace.h"
10
11namespace {
12constexpr uint32_t NoRoot = 1, NoSetuidFixup = 4, KeepCaps = 16, KeepCapsLocked = 32;
13constexpr uint32_t NoAmbientRaise = 64;
14Thread& current() {
15 return *Processor::information().getCurrentThread();
16}
17int invalid() {
18 SYSCALL_ERROR(InvalidArgument);
19 return -1;
20}
21int denied() {
22 SYSCALL_ERROR(NotEnoughPermissions);
23 return -1;
24}
25bool publish(Thread& task, const PosixTaskCredentials& value) {
26 auto next = TaskCredentialsRef::tryAllocate(value);
27 if (!next) {
28 SYSCALL_ERROR(OutOfMemory);
29 return false;
30 }
31 return posix_sandbox_set_credentials(task, next);
32}
33} // namespace
34
35int posix_capability_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4,
36 unsigned long arg5) {
37 TerminationDeferral lifetime;
38 auto next = posix_task_credentials(current());
39 if (arg4 || arg5 || (option != 47 && arg3)) {
40 return invalid();
41 }
42 int result = 0;
43 bool changed = false;
44 switch (option) {
45 case 7: // PR_GET_KEEPCAPS
46 if (arg2) {
47 return invalid();
48 }
49 result = bool(next.secureBits & KeepCaps);
50 break;
51 case 8: // PR_SET_KEEPCAPS
52 if (arg2 > 1) {
53 return invalid();
54 }
55 if (next.secureBits & KeepCapsLocked) {
56 return denied();
57 }
58 next.secureBits = (next.secureBits & ~KeepCaps) | (arg2 ? KeepCaps : 0);
59 changed = true;
60 break;
61 case 23: // PR_CAPBSET_READ
62 if (arg2 > PosixCapabilities::Last) {
63 return invalid();
64 }
65 result = bool(next.bounding & (uint64_t(1) << arg2));
66 break;
67 case 24: // PR_CAPBSET_DROP
68 if (arg2 > PosixCapabilities::Last) {
69 return invalid();
70 }
71 if (!(next.effective & (uint64_t(1) << PosixCapabilities::Setpcap))) {
72 return denied();
73 }
74 next.bounding &= ~(uint64_t(1) << arg2);
75 changed = true;
76 break;
77 case 27: // PR_GET_SECUREBITS
78 if (arg2) {
79 return invalid();
80 }
81 result = next.secureBits;
82 break;
83 case 28: // PR_SET_SECUREBITS
84 if (arg2 & ~0xffUL) {
85 return invalid();
86 }
87 if (!(next.effective & (uint64_t(1) << PosixCapabilities::Setpcap))) {
88 return denied();
89 }
90 for (unsigned bit = 1; bit < 8; bit += 2) {
91 if ((next.secureBits & (1U << bit)) && ((next.secureBits ^ arg2) & (3U << (bit - 1)))) {
92 return denied();
93 }
94 }
95 next.secureBits = arg2;
96 changed = true;
97 break;
98 case 47: // PR_CAP_AMBIENT
99 if (arg2 == 4) {
100 if (arg3) {
101 return invalid();
102 }
103 next.ambient = 0;
104 changed = true;
105 } else {
106 if (arg3 > PosixCapabilities::Last) {
107 return invalid();
108 }
109 const uint64_t bit = uint64_t(1) << arg3;
110 switch (arg2) {
111 case 1:
112 result = bool(next.ambient & bit);
113 break;
114 case 2:
115 if ((next.secureBits & NoAmbientRaise) || !(next.permitted & bit) ||
116 !(next.inheritable & bit)) {
117 return denied();
118 }
119 next.ambient |= bit;
120 changed = true;
121 break;
122 case 3:
123 next.ambient &= ~bit;
124 changed = true;
125 break;
126 default:
127 return invalid();
128 }
129 }
130 break;
131 default:
132 return invalid();
133 }
134 if (changed && !publish(current(), next)) {
135 return -1;
136 }
137 current().setErrno(0);
138 return result;
139}
140
141bool posix_capabilities_exec(Thread& task, uint32_t globalUid) {
142 TerminationDeferral lifetime;
143 auto next = posix_task_credentials(task);
144 const uint64_t oldPermitted = next.permitted;
145 uint32_t root = 0;
146 const bool rootMapped = !next.userNamespace || next.userNamespace->toGlobal(false, 0, root);
147 if (!(next.secureBits & NoRoot) && rootMapped && globalUid == root) {
148 next.permitted = next.inheritable | next.bounding;
149 if (posix_no_new_privs()) {
150 next.permitted &= oldPermitted;
151 }
152 next.effective = next.permitted;
153 } else {
154 // This filesystem does not support executable file capabilities or set-ID transitions.
155 next.permitted = next.effective = next.ambient;
156 }
157 next.secureBits &= ~KeepCaps;
158 return publish(task, next);
159}
160
161bool posix_capabilities_uid_change(Thread& task, uint32_t oldReal, uint32_t oldEffective,
162 uint32_t oldSaved, uint32_t newReal, uint32_t newEffective,
163 uint32_t newSaved, uint32_t namespaceRoot) {
164 if (oldReal == newReal && oldEffective == newEffective && oldSaved == newSaved) {
165 return true;
166 }
167 auto stored = posix_sandbox_credentials(task);
169 if (stored) {
170 next = *stored;
171 } else if (!oldEffective) {
172 next.permitted = next.effective = PosixCapabilities::All;
173 }
174 if (next.secureBits & NoSetuidFixup) {
175 return true;
176 }
177 if ((oldReal == namespaceRoot || oldEffective == namespaceRoot || oldSaved == namespaceRoot) &&
178 newReal != namespaceRoot && newEffective != namespaceRoot && newSaved != namespaceRoot) {
179 if (!(next.secureBits & KeepCaps)) {
180 next.permitted = 0;
181 }
182 next.ambient = 0;
183 }
184 if (oldEffective == namespaceRoot && newEffective != namespaceRoot) {
185 next.effective = 0;
186 } else if (oldEffective != namespaceRoot && newEffective == namespaceRoot) {
187 next.effective = next.permitted;
188 }
189 return publish(task, next);
190}
191
192bool posix_capabilities_fsuid_change(Thread& task, uint32_t oldUid, uint32_t newUid,
193 uint32_t namespaceRoot) {
194 if (oldUid == newUid) {
195 return true;
196 }
197 auto stored = posix_sandbox_credentials(task);
199 if (stored) {
200 next = *stored;
201 } else if (!oldUid) {
202 next.permitted = next.effective = PosixCapabilities::All;
203 }
204 if (next.secureBits & NoSetuidFixup) {
205 return true;
206 }
207 constexpr uint64_t filesystemCaps = (uint64_t(1) << 0) | (uint64_t(1) << 1) | (uint64_t(1) << 2) |
208 (uint64_t(1) << 3) | (uint64_t(1) << 4) | (uint64_t(1) << 9) |
209 (uint64_t(1) << 27);
210 if (oldUid == namespaceRoot && newUid != namespaceRoot) {
211 next.effective &= ~filesystemCaps;
212 } else if (oldUid != namespaceRoot && newUid == namespaceRoot) {
213 next.effective |= next.permitted & filesystemCaps;
214 } else {
215 return true;
216 }
217 return publish(task, next);
218}
static ProcessorInformation & information()
static SharedPointer< T > tryAllocate(Args...)