跳到正文
Hacker News 熱門(buzzing.cc 中文翻譯)·· 2 小時前AI 評分59

《大魚》:利用 OpenBSD 一個存在 37 年的核心漏洞取得 root 權限

《大鱼》:利用 OpenBSD 中一个存在了 37 年的内核漏洞

AI 導讀

Calif 公佈一項 OpenBSD 漏洞利用,透過可追溯至約 37 年前 BSD 原始碼的共享記憶體計數器溢位,配合競態條件和核心越界寫入取得本機 root 權限。相關問題已分別在 errata 026 和 018 修正。文章亦提出安全解除鏈結、遞迴 PTE 映射地址隨機化,以及核心記憶體配置器隨機化等緩解方式。

正文

Big fish. Small counter. Root shell.

Big Fish: an OpenBSD adventure

Calif frequently publishes our offensive research and work, but we otherwise don't draw much attention to the defensive work we do — assisting open source projects with remediation, patch generation, cryptographic improvements, and internal engineering projects we carry out for our clients.

Earlier this year, we reported some vulnerabilities, often along with suggested patches to OpenBSD. One traced back roughly 37 years to the original BSD sources, where a shared memory attachment counter was defined as a signed 16-bit short. Repeatedly forking a process with shared memory attachment records could push that counter past its maximum value of 32,767, causing it to wrap to a negative value. This integer overflow could lead to a use-after-free via a race condition; combined with a separate out-of-bounds write in the kernel, it allowed local privilege escalation to root. The issues were addressed in errata 026 and 018, respectively.

The exploit

We managed to get local root in OpenBSD with KARL / SMAP / SMEP enabled without the use of an information disclosure vulnerability via the following path:

  • Map 127 SysV shared memory segments, unmap them via munmap(), and fork 259 processes to wrap the shared memory attach count to -32,643.
  • Perform heap grooming and layout, using metrics from sysctl(KERN_MALLOCSTATS) (such as kb_total and kb_totalfree) to track allocations and determine when new pages are allocated.
  • Win the race condition using IPC_RMID in one process while attaching a new size-confused shared memory segment in a helper process.
  • Align IP socket allocations and set IP_MULTICAST_TTL to a non-default value of 2 to allocate a persistent 16-byte struct ip_moptions. Its final word packs two 16-bit counters: imo + 12 = imo_num_memberships | imo_max_memberships << 16.
  • Trigger the conditional -1 out-of-bounds write to overwrite both the number of memberships and maximum memberships fields.
  • close() the socket to traverse the group members, triggering atomic decrements that modify the PTE self-referential mapping twice, switching it from user to supervisor accessible mapping, and achieving kernel code execution by overwriting a predictable kernel pointer.
  • Clean up shared memory accounting and restore the overwritten kernel pointer to obtain a root shell.

That's the short of the matter, the longer version is more interesting, including the history of the vulnerability, as a short shm_nattch can be traced back ~37 years ago, in the original BSD sources.

Taking a step back, this vulnerability ended up getting code execution via a write what where primitive, where the write was caused by a TAILQ_REMOVE, the what was our userland memory mapping that was made supervisor mode accessible to bypass SMAP/SMEP, and the where was a predictable kernel pointer location.

Exploit mitigation

While OpenBSD's KARL relinks the kernel binary to randomize both function and data addresses, the kernel's memory allocators remain deterministic. Both the boot-time allocator and the best-fit allocator lack base address randomization. Consequently, early boot allocations consistently land at predictable locations across boots on the same hardware. When these predictable structure allocations contain function pointers, they become ideal targets for exploitation. For instance, the exploit reliably targets the lo0 and enc0 ioctl handlers, overwriting one to gain control flow and using the other to restore the overwritten pointer.

Aside from the aforementioned errata fixes, there's several other approaches that can be taken to either eliminate or reduce the likelihood of those avenues being used again.

Firstly, we can implement safe-unlinking, a technique that checks doubly linked list invariants before performing the unlink memory writes. If the invariants are violated, then a suitable abort() or panic() is called, depending on if user mode or kernel mode, preventing memory corruption from occurring. An example implementation is here. FreeBSD and NetBSD have an implementation of safe-unlinking in their sys/queue.h implementation.

Secondly, we can randomize the self-referential PTE map allocation address so that the targeted atomic decrement locations would not be possible. This is done by using a random byte (.openbsd.randomdata) and selecting a PML4 slot (320 + (random_byte & 127)) at boot time, and installing the recursive entry at that location. The PTE_BASE definition becomes a runtime expression, yielding bases from 0xffffa00000000000 through 0xffffdf8000000000 at 512-GiB intervals. An example implementation is here. In case randomization is not suitable, other options might suffice, such as marking the PTE as read-only, and changing CR0.WP during modification.

Thirdly, the kernel allocator can be made non-deterministic using two approaches; by randomizing the base address where memory allocations occur at boot time, and additionally randomizing the allocation order at run time. The base address randomization is effectively free as the gap it creates can still be used later on; while runtime randomization does incur some overhead during allocation - it may be possible that more in-depth allocator changes could alleviate this impact. Either approach would be sufficient to break the predictable pointer location without additional vulnerabilities, be it software or hardware.

Furthermore, there are existing and upcoming compiler features that can be used to mitigate against entire vulnerabilities, from array bounds sanitizer (a subset of Undefined Behaviour sanitizer) which can eliminate out of bounds fixed size array accesses. Calif has suggested to FreeBSD that the array bounds sanitizer may make a useful exploit mitigation that can be enabled in release builds, along with privately reporting 21 issues that the array bounds sanitizer would have caught; some with local privilege escalation security impact. An upcoming compiler feature that can be used to help mitigate against heap issues is Alloc Tokens, which can be used to isolate pointer containing allocations from non-pointer allocations. This isolation can make overflows from the non-pointer containing allocation much more difficult to exploit by preventing pointer access, along with making vulnerability classes like Use-after-Free unable to use the opposite type of allocation as the replacement memory allocation.

Since its inception, OpenBSD has consistently championed proactive security through source code auditing, the replacement of insecure APIs, advanced password hashing, cryptography, privilege separation, sandboxing, along with memory safety via ASLR and W^X; frequently setting the standard for system and network hardening.

By proactively hardening, and adapting new and existing mitigation options, we can often systematically neutralize entire classes of exploitation primitives; or at the very least, increase the attacker's cost (i.e. additional time investment, additional vulnerabilities needed to exploit an issue, find alternative avenues, etc). Investing in defensive improvements before vulnerabilities are actively targeted raises the bar for attackers, helping ensure robust protection and long-term resilience. In the age of AI-assisted vulnerability discovery, mitigating against entire vulnerability classes, combined with increasing attack complexity only becomes more critical.

Code and PoC →

來源:Hacker News 熱門(buzzing.cc 中文翻譯) · calif.io