Linux: add sanity check in `find_aslr`
Describe the bug
We encountered a situation where Volatility would wrongly assume that a memory image had a virtual ASLR of zero because the first matching init_task was in a fragment of the .data section of the kernel ELF file in the page cache, instead of the one in the running kernel's .data section. The kernel ELF file was opened prior to acquisition and thus fragments of it were scattered in physical memory.
The kernel ELF file's .data section contains the init_task's task_struct with values for the files, thread_pid, fs, ... members initialized to the non-randomized addresses that are also found in the system map.
$ objcopy --dump-section .data=/tmp/linux.data bpfvol3-roboxes-centos9s-vmlinux_extracted-8b0ac.elf
$ readelf --symbols bpfvol3-roboxes-centos9s-vmlinux_extracted-8b0ac.elf | rg init_files
189146: ffffffff83084800 704 OBJECT GLOBAL DEFAULT 28 init_files
$ rg 'init_files' bpfvol3-roboxes-centos9s-vmlinux_extracted-8b0ac.map
171871:ffffffff83084800 D init_files
$ xxd /tmp/linux.data | rg swapper -C 30 | rg 0048
0001b650: 20d2 0883 ffff ffff 0048 0883 ffff ffff ........H......
Volatility currently picks the first valid-looking init_task it finds in the image and thus if such a fragment of the data section precedes the real init_task a virtual ASLR of zero is determined by the following code:
aslr_shift = (
init_task.files.cast("long unsigned int")
- module.get_symbol("init_files").address
)
To identify such "phony" matches I'd propose two heuristics:
"match is phony" <=> .active_mm == init_mmwhere the address ofinit_mmis taken from the system map/json symbols. This works because kernel threads, i.e. tasks with.mm=NULL, "steal" theactive_mmof the previous task when they are scheduled to run on a CPU. Thus, as long as some CPU on the system has been idle at least once theactive_mmof the real init_task will be unequal toinit_mm, even on systems that really had no ASLR. (Theoretically it would be possible to construct an edge case where this is wrong, but it is incredibly unlikely ^^)- There are multiple doubly-linked lists in the init_task's task_struct that are statically initialized to be empty. Thus, a possible heuristic based on this would be: "match is phony" <=>
.tasks.next == .tasks.prev, which should never be the case for the realinit_task.
I personally prefer the latter option due to its simplicity (just tell me if you think so too and I'll happily remove the other one from the PR).
Context Volatility Version: a4b927f
To Reproduce In case you are interested I uploaded some dump+symbol that exhibits this behavior. Download
Process listing plugins will show no output (empty task list), other plugins like kmsg will list dereference invalid pointers and crash.
@atcuno could you give this a look over please, it's very short but I just can't tell if the technique it's using is valid or if there'll be corner cases that might go wrong...