USERSPACE FILE: mlock_user.c KERNEL FILE: lru_mlock_hw.c MAKEFILE: standard kernel module makefile
VIOLATION CHECK: NEW THINGS INTRODUCED WITHOUT DERIVATION: NONE
#1.CALL: make -C /lib/modules/6.14.0-37-generic/build M=/home/r/Desktop/learnlinux/kernel_exercises/lru_mlock_union modules
#1.WORK: compile lru_mlock_hw.c → lru_mlock_hw.ko (kernel object file)
#1.OUTPUT: lru_mlock_hw.ko created, 0 errors
#2.CALL: /tmp/mlock_demo (compiled from mlock_demo.c)
#2.LINE: char *ptr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
#2.RETURN: ptr = 0x7176202de000 (anonymous VMA created)
#2.LINE: ptr[0] = 'X' (triggers page fault)
#2.KERNEL: do_anonymous_page() → alloc_page() → add_to_lru_list()
#2.RESULT: physical page allocated, added to LRU list, struct page created in vmemmap
#3.CALL: insmod lru_mlock_hw.ko target_pid=200505 target_addr=0x767e07605000
#3.KERNEL_FUNC: get_user_pages_remote(mm=0xffff..., addr=0x767e07605000, nr_pages=1)
#3.RETURN: page pointer = 0xfffff7da50167e40
#3.READ: page->lru.next at offset 8
#3.VALUE: lru_next_raw = 0xfffff7da48be2148
#3.INTERPRET: valid kernel pointer → page is linked in LRU list
#3.READ: page->lru.prev at offset 16
#3.VALUE: lru_prev_raw = 0xfffff7da46a9d408
#3.INTERPRET: valid kernel pointer → page is linked in LRU list
#3.READ: *(unsigned int*)(&page->lru + 8) at offset 16 (same bytes as lru.prev)
#3.VALUE: mlock_val = 1185534984
#3.CALCULATION: 0xfffff7da46a9d408 → lower 32 bits = 0x46a9d408 = 1185534984
#3.INTERPRET: GARBAGE (pointer bits read as integer)
#4.CALL: mlock(ptr=0x7176202de000, len=4096)
#4.KERNEL_FUNC: do_mlock() → mlock_fixup() → __mlock_page()
#4.KERNEL_ACTION: remove page from LRU list
#4.KERNEL_ACTION: set page->lru.next = LIST_POISON1 = 0xdead000000000100
#4.KERNEL_ACTION: set page->lru.prev = LIST_POISON2 = 0xdead000000000001
#4.KERNEL_ACTION: mlock_count field (bottom 32 bits of "prev" location) = 1
#4.RETURN: 0 (success)
#5.CALL: rmmod lru_mlock_hw; insmod lru_mlock_hw.ko target_pid=201106 target_addr=0x7176202de000
#5.KERNEL_FUNC: get_user_pages_remote(mm=0xffff..., addr=0x7176202de000, nr_pages=1)
#5.RETURN: page pointer = 0xfffff7da48b9acc0
#5.READ: page->lru.next at offset 8
#5.VALUE: lru_next_raw = 0xdead000000000100
#5.INTERPRET: LIST_POISON1 → page is NOT on any list → removed from LRU
#5.READ: page->lru.prev at offset 16
#5.VALUE: lru_prev_raw = 0xdead000000000001
#5.INTERPRET: LIST_POISON2 variant with count → encodes mlock_count = 1
#5.READ: *(unsigned int*)(&page->lru + 8) at offset 16
#5.VALUE: mlock_val = 1
#5.INTERPRET: VALID mlock count → page has been mlock()'d once
BYTES [16-19] OF struct page:
BEFORE MLOCK:
Read as lru.prev (8 bytes): 0xfffff7da46a9d408 (valid pointer)
Read as mlock_count (4 bytes): 0x46a9d408 = 1185534984 (garbage)
AFTER MLOCK:
Read as lru.prev (8 bytes): 0xdead000000000001 (poison value)
Read as mlock_count (4 bytes): 0x00000001 = 1 (valid count)
┌────────────────────────────────────────────────────────────────────┐
│ MEMORY LAYOUT: BYTES [16-19] OF struct page │
├────────────────────────────────────────────────────────────────────┤
│ BEFORE MLOCK: │
│ Byte 16: 0x08 ─┐ │
│ Byte 17: 0xd4 │→ lower 32 bits of lru.prev pointer │
│ Byte 18: 0xa9 │ = 0x46a9d408 = 1185534984 (garbage as int) │
│ Byte 19: 0x46 ─┘ │
├────────────────────────────────────────────────────────────────────┤
│ AFTER MLOCK: │
│ Byte 16: 0x01 ─┐ │
│ Byte 17: 0x00 │→ mlock_count = 0x00000001 = 1 (valid) │
│ Byte 18: 0x00 │ │
│ Byte 19: 0x00 ─┘ │
└────────────────────────────────────────────────────────────────────┘
GOAL: access mlock_count which C cannot name directly (anonymous struct)
STEP A: &page->lru = address of lru field = page_base + 8 bytes
TYPE: struct list_head *
STEP B: (char *)&page->lru = cast to char* for byte arithmetic
TYPE: char *
VALUE: 0xfffff7da48b9acc0 + 8 (offset of lru in struct page)
STEP C: (char *)&page->lru + 8 = skip 8 bytes (skip lru.next)
= address of lru.prev = address of mlock_count
VALUE: 0xfffff7da48b9acc8 + 8 = 0xfffff7da48b9acd0
STEP D: (unsigned int *)(...) = reinterpret as pointer to uint
TYPE: unsigned int *
STEP E: *(unsigned int *) = dereference, read 4 bytes as integer
VALUE: 1 (after mlock)
VIOLATION CHECK: NEW THINGS INTRODUCED WITHOUT DERIVATION: NONE
heap_ptr1 = 0x5b284d6b42c0
heap_ptr2 = 0x5b284d6b4330
heap_ptr3 = 0x5b284d6b43a0
Page containing ptr1: 0x5b284d6b4000
Page containing ptr2: 0x5b284d6b4000 ← SAME PAGE
Offset of ptr1 in page: 0x2c0 = 704 bytes
DANGER: ptr1 and ptr2 are on SAME PAGE!
mlock(heap_ptr1, 100) succeeded
LOCKED: entire page 0x5b284d6b4000 (4096 bytes, not just 100)
#1. malloc(100) returned 0x5b284d6b42c0
#2. CALCULATION: 0x5b284d6b42c0 & ~0xFFF = 0x5b284d6b4000 (page base)
#3. OFFSET: 0x5b284d6b42c0 & 0xFFF = 0x2c0 = 704 bytes into page
#4. malloc(100) again returned 0x5b284d6b4330
#5. CALCULATION: 0x5b284d6b4330 & ~0xFFF = 0x5b284d6b4000 (SAME page base)
#6. OFFSET: 0x5b284d6b4330 & 0xFFF = 0x330 = 816 bytes into page
#7. BOTH ALLOCATIONS ARE ON SAME PAGE 0x5b284d6b4000
#8. mlock(heap_ptr1, 100) called
#9. KERNEL: rounds down to page boundary = 0x5b284d6b4000
#10. KERNEL: locks ENTIRE page [0x5b284d6b4000, 0x5b284d6b4FFF]
#11. RESULT: ptr2's memory is ALSO locked (unintended side effect)
PROOF: Page containing ptr1 = Page containing ptr2 = 0x5b284d6b4000
mlock(ptr1, 100) → kernel locked page 0x5b284d6b4000
∴ ptr2 is also locked (same page)
Q: Why rmmod before second insmod?
A: module_init() runs ONCE at insmod. To re-run inspection, must reload module.
Q: What address is passed to driver?
A: Userspace VA from mmap() or malloc(). Driver uses get_user_pages_remote() to find physical page.
Q: Does driver see physical or virtual address?
A: Driver receives VA, walks page table, gets struct page* (physical page metadata).
E01. ASKED “why would an int be dirty” - CONFLATED dirty page (modified) with dirty pointer (flag bits).
E02. ASKED “we never defined types” - FAILED TO READ line 11-12 about UNION.
E03. ASKED “what is union” AFTER definition given - DID NOT READ.
E04. ASKED “lru is in union right” 3 TIMES after already stated - NOT READING.
E05. ASKED “so this expands to two ints” - CONFUSED pointer (8 bytes) with int (4 bytes). list_head = 16 bytes, not 8.
E06. ASKED “why 3 in mask” after derivation given - NOT READING derivations.
| E07. ASKED “why ANON | MOVABLE” after source file cited - DID NOT READ kernel source. |
E08. LEFT redundant code at lines 107-108 - did not notice fallback was unnecessary.
E09. ASKED “why UL suffix” - did not know 32-bit vs 64-bit literal problem.
E10. ASKED “but lru does not contain mlock” - CONFUSED struct MEMBER with ADDRESS CALCULATION.
E11. ASKED “C syntax will never allow” - did not know pointer arithmetic is legal C.
E12. ASKED “why passing this address” - did not remember mmap() returned this VA.
E13. ASKED “why rmmod twice” - did not understand module_init() runs once.
E14. ASKED “how does malloc prove page lock” - did not trace the calculation showing same page base.
PATTERN:
PRESCRIPTION:
PID: 203996
VA: 0x7b4b7617a000
Page Ptr: 0xfffff7da4cc48000
BEFORE MLOCK:
lru.next = 0xfffff7da4936b148
lru.prev = 0xfffff7da481a6648
mlock_count = 1209689672 (garbage = 0x481a6648)
flags = 0x17ffffd802082c
AFTER MLOCK:
lru.next = 0xdead000000000100 (poison)
lru.prev = 0xdead000000000001 (poison + count)
mlock_count = 1 (VALID)
flags = 0x17ffffc016082c
DELTA: lru.prev changed from 0x...481a6648 to 0x...00000001
Same bytes [16-19], interpretation changed.
PROGRAM: /tmp/heap_mlock (malloc + mlock demo)
PID: 209538
HEAP MAPPING:
5c6d39a81000-5c6d39a82000 rw-p 00000000 00:00 0 [heap]
HEAP_PTR1 ADDRESS: 0x5c6d39a812c0
PAGE BASE: 0x5c6d39a812c0 & ~0xFFF = 0x5c6d39a81000
OFFSET IN PAGE: 0x5c6d39a812c0 & 0xFFF = 0x2c0 = 704 bytes
DRIVER INSPECTION AFTER mlock(heap_ptr1, 100):
Page Ptr = 0xfffff7da4771c300
lru.next = 0xdead000000000100 (LIST_POISON1)
lru.prev = 0xdead000000000001 (poison + count)
mlock_count = 1 (VALID)
flags = 0x17ffffc0160828
PROOF CHAIN:
#1. malloc(100) returned 0x5c6d39a812c0
#2. mlock(0x5c6d39a812c0, 100) called - requested 100 bytes
#3. Kernel rounded to page: 0x5c6d39a81000
#4. Kernel locked ENTIRE page (4096 bytes)
#5. Driver confirmed: mlock_count = 1 (not 0)
#6. Any other address in range [0x5c6d39a81000, 0x5c6d39a81FFF] is also locked
DEMONSTRATED:
1. struct page union: lru vs mlock_count share bytes [8-23]
2. BEFORE mlock: lru pointers valid, mlock_count garbage
3. AFTER mlock: lru.next/prev = poison, mlock_count = 1
4. mmap page: inspected with driver, proved state change
5. malloc page: inspected with driver, proved mlock(100) locks 4096 bytes