-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuser.rs
More file actions
316 lines (283 loc) · 10.6 KB
/
Copy pathuser.rs
File metadata and controls
316 lines (283 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
//! linux-parity: complete
//! linux-source: vendor/linux/kernel/user.c
//! test-origin: linux:vendor/linux/kernel/user.c
//! Per-uid bookkeeping (`struct user_struct`) — M27a.
//!
//! The "user cache" Linux uses to back per-uid limits: how many processes a
//! uid currently owns, how many open files, how many epoll watches, and the
//! ratelimit state used by `printk_ratelimit` style throttling. Each call to
//! `alloc_uid` increments the per-uid refcount; `free_uid` releases it.
//! The root uid (`KUid(0)`) is pre-inserted as `ROOT_USER` so it never gets
//! evicted from the hash.
//!
//! Reference: vendor/linux/kernel/user.c
//! vendor/linux/include/linux/sched/user.h
//!
//! # Port notes
//!
//! Linux maintains a fixed-size hlist hash (`UIDHASH_SZ`); we use a single
//! `Vec` under a spinlock, which matches the surface API but is O(n) on
//! lookup. Replace with a hash bucket array once the user namespace gain
//! their own per-namespace user cache (Linux ties hashing to the global root
//! ns; user_namespace.uid_map handles the translation before lookup).
//!
//! Per-user-namespace lookup keys: Linux 6.x hashes by `kuid_val(uid)` and
//! does not include `user_ns`, because `struct user_struct` is global. We
//! follow the same scheme — the uid passed in is already namespace-translated
//! by the caller.
extern crate alloc;
use alloc::{sync::Arc, vec::Vec};
use core::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use spin::Mutex;
use crate::kernel::cred::KUid;
use crate::kernel::ucount::{Ucounts, alloc_ucounts, put_ucounts};
/// Per-uid bookkeeping struct. Linux: `struct user_struct` from
/// include/linux/sched/user.h.
pub struct UserStruct {
/// Refcount. Linux: `__count`. Released via `free_uid`.
pub count: AtomicI64,
pub uid: KUid,
/// Number of processes owned by this uid. Linux: `processes`.
pub processes: AtomicI64,
/// Number of pending signals. Linux: `sigpending`.
pub sigpending: AtomicI64,
/// Number of fanotify groups. Linux: `fanotify_listeners`.
pub fanotify_listeners: AtomicI64,
/// Locked-memory page count. Linux: `locked_shm`.
pub locked_shm: AtomicI64,
/// Unix domain socket open count. Linux: `unix_inflight`.
pub unix_inflight: AtomicI64,
/// Pipe buffer pages. Linux: `pipe_bufs`.
pub pipe_bufs: AtomicI64,
/// EPOLL watch count. Linux: `epoll_watches`.
pub epoll_watches: AtomicI64,
/// Last log throttle timestamp (ratelimit_state.begin in jiffies).
/// Linux: `ratelimit` substructure.
pub ratelimit_begin: AtomicU64,
pub ratelimit_burst: AtomicI64,
/// Side reference to the matching `Ucounts` slot in the root user
/// namespace. Populated lazily on first `alloc_uid`.
pub ucounts: Option<Arc<Ucounts>>,
}
// SAFETY: All public fields are atomic or read-only after init.
unsafe impl Send for UserStruct {}
unsafe impl Sync for UserStruct {}
impl UserStruct {
fn new(uid: KUid, ucounts: Option<Arc<Ucounts>>) -> Self {
Self {
count: AtomicI64::new(1),
uid,
processes: AtomicI64::new(0),
sigpending: AtomicI64::new(0),
fanotify_listeners: AtomicI64::new(0),
locked_shm: AtomicI64::new(0),
unix_inflight: AtomicI64::new(0),
pipe_bufs: AtomicI64::new(0),
epoll_watches: AtomicI64::new(0),
ratelimit_begin: AtomicU64::new(0),
ratelimit_burst: AtomicI64::new(0),
ucounts,
}
}
}
/// Linux: `GLOBAL_ROOT_UID`.
pub const GLOBAL_ROOT_UID: KUid = KUid(0);
// ── Hash table ───────────────────────────────────────────────────────────────
struct UserHash {
entries: Vec<Arc<UserStruct>>,
}
impl UserHash {
const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
fn find(&self, uid: KUid) -> Option<Arc<UserStruct>> {
self.entries.iter().find(|u| u.uid == uid).cloned()
}
fn insert(&mut self, u: Arc<UserStruct>) {
self.entries.push(u);
}
fn remove(&mut self, uid: KUid) {
self.entries.retain(|u| u.uid != uid);
}
}
static UID_HASH: Mutex<UserHash> = Mutex::new(UserHash::new());
static ROOT_INIT: Mutex<bool> = Mutex::new(false);
fn ensure_root_inserted() {
let mut done = ROOT_INIT.lock();
if *done {
return;
}
let root = Arc::new(UserStruct::new(GLOBAL_ROOT_UID, None));
// Sticky refcount — root_user is never freed under Linux either.
root.count.store(i64::MAX / 2, Ordering::Relaxed);
UID_HASH.lock().insert(root);
*done = true;
}
// ── Public API ───────────────────────────────────────────────────────────────
/// Look up the `user_struct` for `uid`, taking a reference if found.
///
/// Linux: `find_user` from kernel/user.c:176.
pub fn find_user(uid: KUid) -> Option<Arc<UserStruct>> {
ensure_root_inserted();
let table = UID_HASH.lock();
let u = table.find(uid)?;
u.count.fetch_add(1, Ordering::Relaxed);
Some(u)
}
/// Look up or allocate the `user_struct` for `uid`.
///
/// Linux: `alloc_uid` from kernel/user.c:199. Always returns a live `Arc`
/// with one additional reference held by the caller. The first allocation
/// for a given uid also creates the matching `Ucounts` slot in the root
/// user namespace (the Linux equivalent is `current_user_ns()` at the
/// `find_or_create_user_ns` call site).
pub fn alloc_uid(uid: KUid) -> Arc<UserStruct> {
ensure_root_inserted();
{
let table = UID_HASH.lock();
if let Some(u) = table.find(uid) {
u.count.fetch_add(1, Ordering::Relaxed);
return u;
}
}
// Lazily attach a ucounts slot in the root user namespace. Use a stable
// sentinel (the address of UID_HASH) until the user namespace port hands
// out real namespace pointers.
let ns = &UID_HASH as *const _ as *const core::ffi::c_void;
let uc = alloc_ucounts(ns, uid);
let mut table = UID_HASH.lock();
if let Some(existing) = table.find(uid) {
// Lost a race; release the speculative ucounts ref and return shared.
put_ucounts(uc);
existing.count.fetch_add(1, Ordering::Relaxed);
return existing;
}
let u = Arc::new(UserStruct::new(uid, Some(uc)));
table.insert(u.clone());
u
}
/// Drop a reference previously taken by `find_user`/`alloc_uid`.
///
/// Linux: `free_uid` from kernel/user.c:187. Releases the slot when the
/// last user reference goes away (root is kept pinned by its sticky count).
pub fn free_uid(up: Arc<UserStruct>) {
let prev = up.count.fetch_sub(1, Ordering::Release);
if prev > 1 {
return;
}
let uid = up.uid;
if uid == GLOBAL_ROOT_UID {
// Root user is never freed — bump back so the next free_uid sees it.
up.count.fetch_add(1, Ordering::Relaxed);
return;
}
UID_HASH.lock().remove(uid);
// Detach ucounts last so the Arc<UserStruct> drop releases the final
// table reference cleanly.
drop(up);
}
/// Read the per-uid process count.
pub fn user_process_count(uid: KUid) -> i64 {
if let Some(u) = find_user(uid) {
let v = u.processes.load(Ordering::Acquire);
free_uid(u);
v
} else {
0
}
}
/// Bump the process count for `uid`. Returns the new value, or -EAGAIN
/// when the count would exceed `cap` (when `cap` is non-zero).
pub fn user_inc_processes(uid: KUid, cap: i64) -> i64 {
let u = alloc_uid(uid);
let after = u.processes.fetch_add(1, Ordering::AcqRel) + 1;
if cap > 0 && after > cap {
u.processes.fetch_sub(1, Ordering::Release);
free_uid(u);
return -11; // -EAGAIN
}
free_uid(u);
after
}
/// Decrement the process count. Linux: `__free_uid` after an exiting task.
pub fn user_dec_processes(uid: KUid) {
if let Some(u) = find_user(uid) {
let prev = u.processes.fetch_sub(1, Ordering::AcqRel);
debug_assert!(prev > 0, "processes underflow on uid {:?}", uid);
free_uid(u);
}
}
// ── Test helpers ─────────────────────────────────────────────────────────────
#[cfg(test)]
pub fn reset_for_tests() {
let mut table = UID_HASH.lock();
table.entries.clear();
drop(table);
*ROOT_INIT.lock() = false;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::ucount::reset_for_tests as reset_ucounts;
static TEST_LOCK: spin::Mutex<()> = spin::Mutex::new(());
#[test]
fn alloc_returns_same_struct_for_same_uid() {
let _g = TEST_LOCK.lock();
reset_ucounts();
reset_for_tests();
let a = alloc_uid(KUid(1000));
let b = alloc_uid(KUid(1000));
assert!(Arc::ptr_eq(&a, &b));
free_uid(a);
free_uid(b);
}
#[test]
fn distinct_uids_get_distinct_structs() {
let _g = TEST_LOCK.lock();
reset_ucounts();
reset_for_tests();
let a = alloc_uid(KUid(2000));
let b = alloc_uid(KUid(2001));
assert!(!Arc::ptr_eq(&a, &b));
free_uid(a);
free_uid(b);
}
#[test]
fn root_is_preinserted_and_never_evicted() {
let _g = TEST_LOCK.lock();
reset_ucounts();
reset_for_tests();
let root = find_user(GLOBAL_ROOT_UID).expect("root user present");
free_uid(root);
// Even after free, root must remain in the table.
assert!(find_user(GLOBAL_ROOT_UID).is_some());
}
#[test]
fn free_uid_evicts_after_last_ref() {
let _g = TEST_LOCK.lock();
reset_ucounts();
reset_for_tests();
let u = alloc_uid(KUid(3000));
free_uid(u);
assert!(find_user(KUid(3000)).is_none());
}
#[test]
fn inc_processes_respects_cap() {
let _g = TEST_LOCK.lock();
reset_ucounts();
reset_for_tests();
// Hold a long-lived reference so free_uid inside user_inc_processes
// does not evict the slot between calls. This mirrors Linux, where
// the per-task cred pins the user_struct via cred->user.
let _pin = alloc_uid(KUid(4000));
assert_eq!(user_inc_processes(KUid(4000), 2), 1);
assert_eq!(user_inc_processes(KUid(4000), 2), 2);
assert_eq!(user_inc_processes(KUid(4000), 2), -11);
user_dec_processes(KUid(4000));
user_dec_processes(KUid(4000));
assert_eq!(user_process_count(KUid(4000)), 0);
free_uid(_pin);
}
}