Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(kernel/vm): allocate virtual memory spot #165

Merged
merged 56 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
0d7947e
refactor: use xmas-elf instead of object
JonasKruckenberg Nov 23, 2024
82903c1
refactor(loader): remove `alloc` dependency
JonasKruckenberg Nov 23, 2024
03351d3
refactor: remove `panic-abort` crate
JonasKruckenberg Nov 23, 2024
a015e07
refactor: remove `elf` feature from kmm and move into loader
JonasKruckenberg Nov 23, 2024
15877be
cleanup
JonasKruckenberg Nov 24, 2024
9b59ea8
wip
JonasKruckenberg Nov 27, 2024
42bb36a
feat: `Riscv64Sv39::from_active`
JonasKruckenberg Nov 28, 2024
a7bf4dc
feat(sync): `Once::try_call_once`
JonasKruckenberg Nov 28, 2024
57f3bf2
chore: debug printing for page tables
JonasKruckenberg Nov 28, 2024
34a39d7
chore: bring back kernel parsing
JonasKruckenberg Nov 28, 2024
95aba25
refactor(loader): use `pmm` for memory management
JonasKruckenberg Dec 6, 2024
c3a52ff
refactor: cleanup kernel for refactor
JonasKruckenberg Dec 6, 2024
09fa1b8
refactor: reduce memory size for QEMU
JonasKruckenberg Dec 6, 2024
7687068
remove kmm
JonasKruckenberg Dec 7, 2024
532c1f3
Merge branch 'main' into new-vm2
JonasKruckenberg Dec 7, 2024
60d940c
fmt & fixes
JonasKruckenberg Dec 7, 2024
0c5ffe7
cleanup
JonasKruckenberg Dec 10, 2024
071001d
feat(loader): allocate & map kernel heap
JonasKruckenberg Dec 10, 2024
d52cf83
wip
JonasKruckenberg Dec 10, 2024
33097ee
fix(loader): correctly report memory regions ptr and len
JonasKruckenberg Dec 11, 2024
ed7de8a
wip(wavltree): impl Send and Sync for tree
JonasKruckenberg Dec 11, 2024
f4ec629
fmt
JonasKruckenberg Dec 11, 2024
5e417bd
refactor: move `vmm` crate into kernel
JonasKruckenberg Dec 11, 2024
5abeeed
wip
JonasKruckenberg Dec 11, 2024
d55558a
refactor(pmm): remove overengineered arch trait
JonasKruckenberg Dec 12, 2024
ae5e4ab
wavltree fixes
JonasKruckenberg Dec 12, 2024
4307fa3
cleanup
JonasKruckenberg Dec 12, 2024
e5c0581
fmt
JonasKruckenberg Dec 12, 2024
51c38a1
Merge branch 'main' into new-vm2
JonasKruckenberg Dec 12, 2024
21029ae
Merge branch 'main' into new-vm2
JonasKruckenberg Dec 12, 2024
470dcd6
Update once.rs
JonasKruckenberg Dec 12, 2024
102c70c
Merge branch 'main' into new-vm2
JonasKruckenberg Dec 12, 2024
7a1a735
cleanup
JonasKruckenberg Dec 12, 2024
f602dd3
refactor: report FDT memory region through `BootInfo.memory_regions` …
JonasKruckenberg Dec 12, 2024
b2610e7
cleanup
JonasKruckenberg Dec 12, 2024
7a623b6
fix: report FDT through memory regions array
JonasKruckenberg Dec 12, 2024
a144dcd
fix: correctly handle unmapping
JonasKruckenberg Dec 12, 2024
2f2de68
clippy
JonasKruckenberg Dec 12, 2024
12ad8bd
fix: use virtual FDT address is test runner
JonasKruckenberg Dec 12, 2024
ae78433
refactor: rename `AddressSpace:: phys_offset` to `AddressSpace:: phys…
JonasKruckenberg Dec 12, 2024
62d6be3
docs
JonasKruckenberg Dec 12, 2024
a38bb24
fmt
JonasKruckenberg Dec 12, 2024
2456619
feat(wavltree): implement lifecycle hooks
JonasKruckenberg Dec 13, 2024
ac66b51
feat(kernel/vm): implement efficient gap search state
JonasKruckenberg Dec 13, 2024
7d3a63c
feat(kernel/vm): use prng for ASLR
JonasKruckenberg Dec 13, 2024
0e7f7ae
refactor: remove ambiguous and broken `cursor`/`cursor_mut` in favor …
JonasKruckenberg Dec 12, 2024
a91258b
chore(wavltree): release v0.0.1 (#161)
github-actions[bot] Dec 12, 2024
3976974
chore(deps): update rust crate target-lexicon to 0.13.0 (#157)
renovate[bot] Dec 12, 2024
590938a
feat(kernel): parse FDT on startup
JonasKruckenberg Dec 13, 2024
4f9616f
fix: seed ASLR prng from provided seed
JonasKruckenberg Dec 13, 2024
7401f98
feat(kernel/vm): allocate virtual memory spot
JonasKruckenberg Dec 13, 2024
10be9a9
wip: test the pick_spot impl
JonasKruckenberg Dec 13, 2024
d35d64c
clippy & fmt
JonasKruckenberg Dec 14, 2024
88a3941
refactor: move find_spot tests into tests
JonasKruckenberg Dec 14, 2024
6a0c727
fmt
JonasKruckenberg Dec 14, 2024
077ed06
update from main
JonasKruckenberg Dec 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(kernel/vm): allocate virtual memory spot
Implement the `find_spot` method that searches the augmented wavltree of mappings for a suitable gap given a `Layout`. The resulting spot will be randomized, but left-leaning.
Further method accepts an `entropy_bits` parameter that can be used to tune the sparseness of produced allocations. More consideration for this parameter can be found in the docs (docs/aslr.md)
  • Loading branch information
JonasKruckenberg committed Dec 14, 2024
commit 7401f98539d6db73f2ca6380cd8c8ee11576c1ed
137 changes: 137 additions & 0 deletions kernel/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,143 @@ impl AddressSpace {
pub fn decommit(&mut self, range: Range<VirtualAddress>) {
todo!()
}

// behaviour:
// - find the leftmost gap that satisfies the size and alignment requirements
// - starting at the root,
pub fn find_spot(&mut self, layout: Layout, entropy: u8) -> VirtualAddress {
log::trace!("finding spot for {layout:?} entropy {entropy}");

let max_candidate_spaces: usize = 1 << entropy;
log::trace!("max_candidate_spaces {max_candidate_spaces}");

let selected_index: usize = self
.prng
.as_mut()
.map(|prng| prng.sample(Uniform::new(0, max_candidate_spaces)))
.unwrap_or_default();

let spot = match self.find_spot_at_index(selected_index, layout) {
Ok(spot) => spot,
Err(0) => panic!("out of virtual memory"),
Err(candidate_spot_count) => {
log::trace!("couldn't find spot in first attempt (max_candidate_spaces {max_candidate_spaces}), retrying with (candidate_spot_count {candidate_spot_count})");
let selected_index: usize = self
.prng
.as_mut()
.unwrap()
.sample(Uniform::new(0, candidate_spot_count));

self.find_spot_at_index(selected_index, layout).unwrap()
}
};
log::trace!("picked spot {spot:?}");

spot
}

pub fn find_spot_at_index(
&self,
mut target_index: usize,
layout: Layout,
) -> Result<VirtualAddress, usize> {
log::trace!("attempting to find spot for {layout:?} at index {target_index}");

let spots_in_range = |layout: Layout, range: Range<VirtualAddress>| -> usize {
((range.end.sub_addr(range.start) - layout.size()) >> layout.align().ilog2()) + 1
};

let mut candidate_spot_count = 0;

const KERNEL_ASPACE_BASE: VirtualAddress = VirtualAddress::new(0xffffffc000000000);

// see if there is a suitable gap between the start of the address space and the first mapping
if let Some(root) = self.tree.root().get() {
let gap_size = root.min_first_byte.sub_addr(KERNEL_ASPACE_BASE);
let aligned_gap = KERNEL_ASPACE_BASE.align_up(layout.align())
..KERNEL_ASPACE_BASE.add(gap_size).align_down(layout.align());
let spot_count = spots_in_range(layout, aligned_gap.clone());
candidate_spot_count += spot_count;
if target_index < spot_count {
return Ok(aligned_gap
.start
.add(target_index << layout.align().ilog2()));
}
target_index -= spot_count;
}

let mut maybe_node = self.tree.root().get();
let mut already_visited = VirtualAddress::default();

while let Some(node) = maybe_node {
if node.max_gap >= layout.size() {
if let Some(left) = node.links.left() {
let left = unsafe { left.as_ref() };

if left.max_gap >= layout.size() && left.max_last_byte > already_visited {
maybe_node = Some(left);
continue;
}

let gap_base = left.max_last_byte;
let gap_size = node.range.end.sub_addr(left.max_last_byte);
let aligned_gap = gap_base.align_up(layout.align())
..gap_base.add(gap_size).align_down(layout.align());
let spot_count = spots_in_range(layout, aligned_gap.clone());

candidate_spot_count += spot_count;
if target_index < spot_count {
return Ok(aligned_gap
.start
.add(target_index << layout.align().ilog2()));
}
target_index -= spot_count;
}

if let Some(right) = node.links.right() {
let right = unsafe { right.as_ref() };

let gap_base = node.range.end;
let gap_size = right.min_first_byte.sub_addr(node.range.end);
let aligned_gap = gap_base.align_up(layout.align())
..gap_base.add(gap_size).align_down(layout.align());
let spot_count = spots_in_range(layout, aligned_gap.clone());

candidate_spot_count += spot_count;
if target_index < spot_count {
return Ok(aligned_gap
.start
.add(target_index << layout.align().ilog2()));
}
target_index -= spot_count;

if right.max_gap >= layout.size() && right.max_last_byte > already_visited {
maybe_node = Some(right);
continue;
}
}
}
already_visited = node.max_last_byte;
maybe_node = node.links.parent().map(|ptr| unsafe { ptr.as_ref() });
}

// see if there is a suitable gap between the end of the last mapping and the end of the address space
if let Some(root) = self.tree.root().get() {
let gap_size = usize::MAX - root.max_last_byte.as_raw();
let aligned_gap = root.max_last_byte.align_up(layout.align())
..root.max_last_byte.add(gap_size).align_down(layout.align());
let spot_count = spots_in_range(layout, aligned_gap.clone());
candidate_spot_count += spot_count;
if target_index < spot_count {
return Ok(aligned_gap
.start
.add(target_index << layout.align().ilog2()));
}
target_index -= spot_count;
}

Err(candidate_spot_count)
}
}

pin_project! {
Expand Down
1 change: 1 addition & 0 deletions manual/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

- [Overview of k23's Architecture](overview.md)
- [Boot Flow](boot_flow.md)
- [Address Space Layout Randomization (ASLR)](aslr.md)

# CPU Architectures

Expand Down
49 changes: 49 additions & 0 deletions manual/src/aslr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Address Space Layout Randomization

Address-space layout randomization is a security technique that - as the name implies - randomizes
the placement of various objects in virtual memory. This well known technique defends against attacks such as
return-oriented programming (ROP) where an attacker chains together instruction sequences of legitimate programs
(called "gadgets") to archive privilege escalation.

Randomizing the placement of objects makes these techniques much harder since now an attacker has to correctly guess
the address from a potentially huge number of possibilities.

## KASLR

k23 randomizes the location of the kernel, stacks, TLS regions and heap at boot time.

## ASLR in k23

k23 implements more advanced userspace ASLR that other operating systems, it not only randomizes the placement of
WASM executable code, tables, globals, and memories; but also the location of individual WASM functions at each program
startup (a similar technique is used by the Linux kernel called function-grained kernel address space layout randomization (FGKASLR))

TODO explain more in detail

## ASLR Entropy

Entropy determines how "spread out" allocations are in the address space
higher values mean a more sparse address space, this is configured through the `entropy_bits` option (TODO).

Ideally the number would be as high as possible, since more entropy means harder to defeat
ASLR. However, a sparser address space requires more memory for page tables and a higher
value for entropy means allocating virtual memory takes longer (more misses the search function
that searches for free gaps). The maximum entropy value also depends on the target architecture
and chosen memory mode:

| Architecture | Virtual Address Usable Bits | Max Entropy Bits |
|------------------------|-----------------------------|------------------|
| Riscv32 Sv32 | 32 | 19 |
| Riscv64 Sv39 | 39 | 26 |
| Riscv64 Sv48 | 48 | 35 |
| Riscv64 Sv57 | 57 | 44 |
| x86_64 | 48 | 35 |
| aarch64 3 TLB lvls | 39 | 26 |
| aarch64 4 TLB lvls | 48 | 35 |

In conclusion, the best value for `entropy_bits` depends on a lot of factors and should be tuned
for best results trading off sparseness and runtime complexity for better security.

Note also that for e.g. Riscv64 Sv57 it might not even be desirable to use all 44 bits of
available entropy since the address space itself is already huge and performance might degrade
too much.