1use seccomp::*;
30use std::alloc::{GlobalAlloc, Layout};
31use std::fs::File;
32use std::io::{BufRead, BufReader, Write};
33use std::sync::Mutex;
34use std::sync::atomic::{AtomicI32, Ordering};
35
36const OOM_MSG: &str = "requires more memory space to initialize BuddyAlloc";
42const LEAF_ALIGN_ERROR_MSG: &str = "leaf size must be align to 16 bytes";
43pub const MIN_LEAF_SIZE_ALIGN: usize = 16;
45
46const fn block_size_2base(k: usize, leaf2base: usize) -> usize {
47 (1 << k) << leaf2base
48}
49
50const fn nblock(k: usize, entries_size: usize) -> usize {
51 1 << (entries_size - k - 1)
52}
53
54const fn roundup(n: usize, sz2base: usize) -> usize {
55 (((n - 1) >> sz2base) + 1) << sz2base
56}
57
58fn log2(mut n: usize) -> usize {
59 let mut k = 0;
60 while n > 1 {
61 k += 1;
62 n >>= 1;
63 }
64 k
65}
66
67fn bit_isset(bit_array: *const u8, i: usize) -> bool {
68 unsafe {
69 let b = bit_array.add(i >> 3);
70 let m = 1 << (i % 8);
71 *b & m == m
72 }
73}
74
75fn bit_set(bit_array: *mut u8, i: usize) {
76 unsafe {
77 let b = bit_array.add(i >> 3);
78 let m = 1 << (i % 8);
79 *b |= m;
80 }
81}
82
83fn bit_clear(bit_array: *mut u8, i: usize) {
84 debug_assert!(bit_isset(bit_array, i));
85 unsafe {
86 let b = bit_array.add(i >> 3);
87 let m = 1 << (i % 8);
88 *b &= !m;
89 }
90}
91
92pub fn first_up_k(n: usize, leaf_size: usize) -> usize {
94 let mut k = 0;
95 let mut size = leaf_size;
96 while size < n {
97 k += 1;
98 size <<= 1;
99 }
100 k
101}
102
103struct Node {
104 next: *mut Node,
105 prev: *mut Node,
106}
107
108impl Node {
109 fn init(list: *mut Node) {
110 unsafe {
111 (*list).next = list;
112 (*list).prev = list;
113 }
114 }
115
116 fn remove(list: *mut Node) {
117 unsafe {
118 (*(*list).prev).next = (*list).next;
119 (*(*list).next).prev = (*list).prev;
120 }
121 }
122
123 fn pop(list: *mut Node) -> *mut Node {
124 debug_assert!(!Self::is_empty(list));
125 let n_list = unsafe { (*list).next };
126 Self::remove(n_list);
127 n_list
128 }
129
130 fn push(list: *mut Node, p: *mut u8) {
131 let p = p.cast::<Node>();
132 unsafe {
133 let n_list: Node = Node {
134 prev: list,
135 next: (*list).next,
136 };
137 p.write(n_list);
139 (*(*list).next).prev = p;
140 (*list).next = p;
141 }
142 }
143
144 fn is_empty(list: *const Node) -> bool {
145 unsafe { std::ptr::eq((*list).next, list) }
146 }
147}
148
149struct Entry {
150 free: *mut Node,
151 alloc: *mut u8,
153 split: *mut u8,
155}
156
157impl Default for Entry {
158 fn default() -> Self {
159 Entry {
160 free: core::ptr::null_mut(),
161 alloc: core::ptr::null_mut(),
162 split: core::ptr::null_mut(),
163 }
164 }
165}
166
167#[derive(Clone, Copy)]
168pub struct BuddyAllocParam {
169 base_addr: *const u8,
171 len: usize,
173 leaf_size: usize,
175 zero_filled: bool,
178}
179
180impl BuddyAllocParam {
181 pub const fn new(base_addr: *const u8, len: usize, leaf_size: usize) -> Self {
185 BuddyAllocParam {
186 base_addr,
187 len,
188 leaf_size,
189 zero_filled: false,
190 }
191 }
192}
193
194pub struct BuddyAlloc {
195 base_addr: usize,
197 end_addr: usize,
199 unavailable: usize,
201 entries: *mut Entry,
202 entries_size: usize,
203 leaf2base: usize,
205}
206
207impl BuddyAlloc {
208 pub unsafe fn new(param: BuddyAllocParam) -> Self {
214 unsafe {
215 let BuddyAllocParam {
216 base_addr,
217 len,
218 leaf_size,
219 zero_filled,
220 } = param;
221 let mut base_addr = base_addr as usize;
222 let end_addr = base_addr + len;
223 assert!(
224 leaf_size % MIN_LEAF_SIZE_ALIGN == 0 && leaf_size != 0,
225 "{}",
226 LEAF_ALIGN_ERROR_MSG
227 );
228 let leaf2base = log2(leaf_size);
229 base_addr = roundup(base_addr, leaf2base);
230 let entries_size = log2((end_addr - base_addr) >> leaf2base) + 2;
234
235 let used_bytes = core::mem::size_of::<Entry>() * entries_size;
237 debug_assert!(end_addr >= base_addr + used_bytes, "{}", OOM_MSG);
238 let entries = base_addr as *mut Entry;
239 base_addr += used_bytes;
240
241 let buddy_list_size = core::mem::size_of::<Node>();
242 for k in 0..entries_size {
244 debug_assert!(end_addr >= base_addr + buddy_list_size, "{}", OOM_MSG);
246 let entry = entries.add(k).as_mut().expect("entry");
247 entry.free = base_addr as *mut Node;
248 if !zero_filled {
249 core::ptr::write_bytes(entry.free, 0, buddy_list_size);
250 }
251 Node::init(entry.free);
252 base_addr += buddy_list_size;
253 }
254
255 for k in 0..entries_size {
257 let used_bytes = roundup(nblock(k, entries_size), 3) >> 3;
260 debug_assert!(end_addr >= base_addr + used_bytes, "{}", OOM_MSG);
261 let entry = entries.add(k).as_mut().expect("entry");
262 entry.alloc = base_addr as *mut u8;
263 if !zero_filled {
265 core::ptr::write_bytes(entry.alloc, 0, used_bytes);
266 }
267 base_addr += used_bytes;
268 }
269
270 for k in 1..entries_size {
272 let used_bytes = roundup(nblock(k, entries_size), 3) >> 3;
275 debug_assert!(end_addr >= base_addr + used_bytes, "{}", OOM_MSG);
276 let entry = entries.add(k).as_mut().expect("entry");
277 entry.split = base_addr as *mut u8;
278 if !zero_filled {
279 core::ptr::write_bytes(entry.split, 0, used_bytes);
280 }
281 base_addr += used_bytes;
282 }
283
284 base_addr = roundup(base_addr, leaf2base);
286 assert!(end_addr >= base_addr, "{}", OOM_MSG);
287 debug_assert_eq!(
288 (base_addr >> leaf2base) << leaf2base,
289 base_addr,
290 "misalignment"
291 );
292
293 let mut allocator = BuddyAlloc {
294 base_addr,
295 end_addr,
296 entries,
297 entries_size,
298 leaf2base,
299 unavailable: 0,
300 };
301 allocator.init_free_list();
302 allocator
303 }
304 }
305
306 fn init_free_list(&mut self) {
307 let mut base_addr = self.base_addr;
308 let end_addr = self.end_addr;
309 let entries_size = self.entries_size;
310
311 for k in (0..(entries_size - 1)).rev() {
313 let block_size = block_size_2base(k, self.leaf2base);
314 let entry = self.entry(k);
315 let parent_entry = self.entry(k + 1);
316
317 while base_addr + block_size <= end_addr {
319 debug_assert!(!bit_isset(
320 entry.alloc,
321 self.block_index(k, base_addr as *const u8)
322 ));
323 Node::push(entry.free, base_addr as *mut u8);
324 let block_index = self.block_index(k, base_addr as *const u8);
326 if block_index & 1 == 0 {
327 let parent_index = self.block_index(k + 1, base_addr as *const u8);
328 bit_set(parent_entry.alloc, parent_index);
329 bit_set(parent_entry.split, parent_index);
330 }
331 base_addr += block_size;
332 }
333
334 let n = nblock(k, entries_size);
336 let unavailable_block_index = self.block_index(k, base_addr as *const u8);
337 debug_assert!(unavailable_block_index < n);
338 bit_set(entry.alloc, unavailable_block_index);
339 }
340
341 self.unavailable = end_addr - base_addr;
342 }
343
344 pub fn malloc(&mut self, nbytes: usize) -> *mut u8 {
345 let fk = first_up_k(nbytes, 1 << self.leaf2base);
346 let mut k = match (fk..self.entries_size).find(|&k| !Node::is_empty(self.entry(k).free)) {
347 Some(k) => k,
348 None => return core::ptr::null_mut(),
349 };
350 let p: *mut u8 = Node::pop(self.entry(k).free) as *mut u8;
351 bit_set(self.entry(k).alloc, self.block_index(k, p));
352 while k > fk {
353 let q: *mut u8 = (p as usize + block_size_2base(k - 1, self.leaf2base)) as *mut u8;
354 bit_set(self.entry(k).split, self.block_index(k, p));
355 let parent_entry = self.entry(k - 1);
356 bit_set(parent_entry.alloc, self.block_index(k - 1, p));
357 debug_assert!(!bit_isset(parent_entry.alloc, self.block_index(k - 1, q)));
358 Node::push(parent_entry.free, q);
359 k -= 1;
360 }
361 debug_assert_eq!(
362 ((p as usize) >> self.leaf2base) << self.leaf2base,
363 p as usize,
364 "misalignment"
365 );
366 p
367 }
368
369 pub fn free(&mut self, mut p: *mut u8) {
370 let mut k = self.find_k_for_p(p);
371 while k < (self.entries_size - 1) {
372 let block_index = self.block_index(k, p);
373 let entry = self.entry(k);
374 bit_clear(entry.alloc, block_index);
375 let is_head = block_index & 1 == 0;
376 let buddy = if is_head {
377 block_index + 1
378 } else {
379 block_index - 1
380 };
381 if bit_isset(entry.alloc, buddy) {
382 break;
383 }
384 let q = self.block_addr(k, buddy);
390 Node::remove(q as *mut Node);
391 if !is_head {
392 p = q as *mut u8;
393 }
394 bit_clear(self.entry(k + 1).split, self.block_index(k + 1, p));
395 k += 1;
396 }
397 debug_assert!(!bit_isset(self.entry(k).alloc, self.block_index(k, p)));
398 Node::push(self.entry(k).free, p);
399 }
400
401 fn entry(&self, i: usize) -> &Entry {
402 debug_assert!(i < self.entries_size, "index out of range");
403 unsafe { self.entries.add(i).as_ref().expect("entry") }
404 }
405
406 fn find_k_for_p(&self, p: *const u8) -> usize {
408 for k in 0..(self.entries_size - 1) {
409 if bit_isset(self.entry(k + 1).split, self.block_index(k + 1, p)) {
410 debug_assert!(bit_isset(self.entry(k).alloc, self.block_index(k, p)));
411 return k;
412 }
413 }
414 0
415 }
416
417 fn block_index(&self, k: usize, p: *const u8) -> usize {
419 if (p as usize) < self.base_addr {
420 panic!("out of memory");
422 }
423 let n = p as usize - self.base_addr;
424 let index = (n >> k) >> self.leaf2base;
426 debug_assert!(index < nblock(k, self.entries_size));
427 index
428 }
429
430 fn block_addr(&self, k: usize, i: usize) -> usize {
432 let n = (i << k) << self.leaf2base;
434 self.base_addr + n
435 }
436}
437
438pub struct UserAllocator {
440 buddy_alloc_param: BuddyAllocParam,
441 inner_buddy_alloc: Mutex<Option<BuddyAlloc>>,
442}
443
444impl UserAllocator {
445 pub const fn new(buddy_alloc_param: BuddyAllocParam) -> Self {
446 UserAllocator {
447 inner_buddy_alloc: Mutex::new(None),
448 buddy_alloc_param,
449 }
450 }
451
452 unsafe fn fetch_buddy_alloc<R, F: FnOnce(&mut BuddyAlloc) -> R>(&self, f: F) -> R {
453 unsafe {
454 let mut inner = self.inner_buddy_alloc.lock().unwrap();
455 let alloc = inner.get_or_insert_with(|| BuddyAlloc::new(self.buddy_alloc_param));
456 f(alloc)
457 }
458 }
459
460 pub fn disable_mmap(&self) -> Result<(), Box<dyn std::error::Error>> {
462 let mut ctx = seccomp::Context::default(Action::Allow)?;
463 let syscall_nr = libc::SYS_mmap as usize;
464 let cmp = Compare::arg(0)
465 .using(Op::MaskedEq)
466 .with(0)
467 .build()
468 .ok_or("Failed to build seccomp filter")?;
469
470 let rule = Rule::new(syscall_nr, cmp, Action::Errno(libc::EPERM));
471 ctx.add_rule(rule)?;
472 ctx.load()?;
473
474 Ok(())
475 }
476
477 pub fn lock_memory(&self) {
478 unsafe {
479 VM.save().ok();
480
481 let new_rlimit = libc::rlimit {
483 rlim_cur: libc::RLIM_INFINITY,
484 rlim_max: libc::RLIM_INFINITY,
485 };
486 let res = libc::setrlimit(libc::RLIMIT_MEMLOCK, &new_rlimit);
487 if res != 0 {
488 panic!("setrlimit failed with error code: {}", res);
489 }
490
491 let res = libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE);
493 if res != 0 {
494 panic!("mlockall failed with error code: {}", res);
495 }
496
497 let ptr = (&raw mut HEAP.0) as *mut u8 as *mut libc::c_void;
499 libc::madvise(ptr, HEAP_SIZE, libc::MADV_HUGEPAGE);
500 }
501 }
502
503 pub fn unlock_memory(&self) {
504 VM.restore().ok();
505 }
506}
507
508unsafe impl GlobalAlloc for UserAllocator {
510 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
511 unsafe {
512 let bytes = layout.size();
513 self.fetch_buddy_alloc(|alloc| alloc.malloc(bytes))
514 }
515 }
516
517 unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
518 unsafe {
519 self.fetch_buddy_alloc(|alloc| alloc.free(ptr));
520 }
521 }
522}
523
524unsafe impl Sync for UserAllocator {}
525
526const HEAP_SIZE: usize = 64 * 1024 * 1024; const LEAF_SIZE: usize = 64;
529
530#[repr(align(4096))]
531struct AlignedHeap<const N: usize>([u8; N]);
532
533static mut HEAP: AlignedHeap<HEAP_SIZE> = AlignedHeap([0u8; HEAP_SIZE]);
535
536#[cfg_attr(not(test), global_allocator)]
546pub static ALLOCATOR: UserAllocator = unsafe {
547 UserAllocator::new(BuddyAllocParam::new(
548 (&raw const HEAP.0) as *const u8,
549 HEAP_SIZE,
550 LEAF_SIZE,
551 ))
552};
553
554struct VmSettings {
559 saved_compact_unevictable_allowed: AtomicI32,
569}
570
571impl VmSettings {
572 fn read_procfs(&self, file_path: &str) -> Result<i32, String> {
574 let file = File::open(file_path)
576 .map_err(|err| format!("Failed to open {}: {}", file_path, err))?;
577
578 let reader = BufReader::new(file);
579
580 let line = reader
581 .lines()
582 .next()
583 .ok_or_else(|| format!("File is empty: {}", file_path))?
584 .map_err(|err| format!("Failed to read line from {}: {}", file_path, err))?;
585
586 line.trim()
587 .parse::<i32>()
588 .map_err(|err| format!("Failed to parse {}: {}", file_path, err))
589 }
590
591 fn write_procfs(&self, file_path: &str, value: i32) -> Result<(), String> {
593 let mut file = File::create(file_path)
595 .map_err(|err| format!("Failed to open {}: {}", file_path, err))?;
596
597 write!(file, "{}", value)
599 .map_err(|err| format!("Failed to write to {}: {}", file_path, err))
600 }
601
602 fn save(&self) -> Result<(), String> {
604 let compact_unevictable_allowed = "/proc/sys/vm/compact_unevictable_allowed";
605 let value = self.read_procfs(compact_unevictable_allowed)?;
606 self.saved_compact_unevictable_allowed
607 .store(value, Ordering::Relaxed);
608 self.write_procfs(compact_unevictable_allowed, 0)?;
609
610 Ok(())
611 }
612
613 fn restore(&self) -> Result<(), String> {
615 let compact_unevictable_allowed = "/proc/sys/vm/compact_unevictable_allowed";
616 let value = self
617 .saved_compact_unevictable_allowed
618 .load(Ordering::Relaxed);
619 self.write_procfs(compact_unevictable_allowed, value)?;
620
621 Ok(())
622 }
623}
624
625static VM: VmSettings = VmSettings {
627 saved_compact_unevictable_allowed: AtomicI32::new(0),
628};