Improving `kconfig` (#3145)

* improving kconfig

* adding option to specify config file

* changed based on suggestions
pull/3147/head
jxuanli 5 months ago committed by GitHub
parent 4ee3ce2e4f
commit 040636ef2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -74,7 +74,7 @@
- [kbase](kernel/kbase.md) - Finds the kernel virtual base address.
- [kchecksec](kernel/kchecksec.md) - Checks for kernel hardening configuration options.
- [kcmdline](kernel/kcmdline.md) - Return the kernel commandline (/proc/cmdline).
- [kconfig](kernel/kconfig.md) - Outputs the kernel config (requires CONFIG_IKCONFIG).
- [kconfig](kernel/kconfig.md) - Outputs the kernel config.
- [kdmesg](kernel/kdmesg.md) - Displays the kernel ring buffer (dmesg) contents.
- [klookup](kernel/klookup.md) - Lookup kernel symbols
- [kmod](kernel/kmod.md) - Displays the loaded Linux kernel modules.

@ -2,11 +2,11 @@
# kconfig
```text
usage: kconfig [-h] [config_name]
usage: kconfig [-h] [-l FILE_PATH] [config_name]
```
Outputs the kernel config (requires CONFIG_IKCONFIG).
Outputs the kernel config.
### Positional arguments
|Positional Argument|Help|
@ -18,6 +18,7 @@ Outputs the kernel config (requires CONFIG_IKCONFIG).
|Short|Long|Help|
| :--- | :--- | :--- |
|-h|--help|show this help message and exit|
|-l|--load|load kernel config file|
<!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. -->
<!-- ------------\>8---- ----\>8---- ----\>8------------ -->

@ -50,26 +50,6 @@ def has_debug_syms() -> bool:
)
# NOTE: This implies requires_debug_syms(), as it is needed for kconfig() to return non-None
def requires_kconfig(default: D = None) -> Callable[[Callable[P, T]], Callable[P, T | D]]:
def decorator(f: Callable[P, T]) -> Callable[P, T | D]:
@functools.wraps(f)
def func(*args: P.args, **kwargs: P.kwargs) -> T | D:
if kconfig():
return f(*args, **kwargs)
# If the user doesn't want an exception thrown when CONFIG_IKCONFIG is
# not enabled, they can instead provide a default return value
if default is not None:
return default
raise Exception(f"Function {f.__name__} requires CONFIG_IKCONFIG enabled in kernel")
return func
return decorator
def requires_debug_syms(default: D = None) -> Callable[[Callable[P, T]], Callable[P, T | D]]:
def decorator(f: Callable[P, T]) -> Callable[P, T | D]:
@functools.wraps(f)
@ -130,36 +110,28 @@ def get_first_kernel_ro() -> pwndbg.lib.memory.Page | None:
return None
def load_kconfig() -> pwndbg.lib.kernel.kconfig.Kconfig | None:
@pwndbg.lib.cache.cache_until("start")
def kconfig() -> pwndbg.lib.kernel.kconfig.Kconfig | None:
global _kconfig
config_start, config_end = None, None
if has_debug_syms():
config_start = pwndbg.aglib.symbol.lookup_symbol_addr("kernel_config_data")
config_end = pwndbg.aglib.symbol.lookup_symbol_addr("kernel_config_data_end")
else:
mapping = get_first_kernel_ro()
results = list(pwndbg.search.search(b"IKCFG_ST", mappings=[mapping]))
if len(results) == 0:
return None
config_start = results[0] + len("IKCFG_ST")
config_end = list(pwndbg.search.search(b"IKCFG_ED", start=config_start))[0]
result = next(pwndbg.search.search(b"IKCFG_ST", mappings=[mapping]), None)
if result is not None:
config_start = result + len("IKCFG_ST")
config_end = next(pwndbg.search.search(b"IKCFG_ED", start=config_start), None)
if config_start is None or config_end is None:
return None
_kconfig = pwndbg.lib.kernel.kconfig.Kconfig(None)
return _kconfig
config_size = config_end - config_start
compressed_config = pwndbg.aglib.memory.read(config_start, config_size)
return pwndbg.lib.kernel.kconfig.Kconfig(compressed_config)
@pwndbg.lib.cache.cache_until("start")
def kconfig() -> pwndbg.lib.kernel.kconfig.Kconfig | None:
global _kconfig
if _kconfig is None:
_kconfig = load_kconfig()
elif len(_kconfig) == 0:
return None
_kconfig = pwndbg.lib.kernel.kconfig.Kconfig(compressed_config)
return _kconfig
@ -193,15 +165,6 @@ def krelease() -> Tuple[int, ...]:
raise Exception("Linux version tuple not found")
@requires_kconfig()
@pwndbg.lib.cache.cache_until("start")
def is_kaslr_enabled() -> bool:
if "CONFIG_RANDOMIZE_BASE" not in kconfig():
return False
return "nokaslr" not in kcmdline()
def get_idt_entries() -> List[pwndbg.lib.kernel.structs.IDTEntry]:
"""
Retrieves the IDT entries from memory.

@ -157,12 +157,6 @@ class SlabCache:
@property
def random(self) -> int:
if not kernel.kconfig():
try:
return int(self._slab_cache["random"])
except pwndbg.dbg_mod.Error:
return 0
return (
int(self._slab_cache["random"]) if "SLAB_FREELIST_HARDENED" in kernel.kconfig() else 0
)

@ -109,14 +109,6 @@ parser = argparse.ArgumentParser(description="Checks for kernel hardening config
def kchecksec() -> None:
kconfig = pwndbg.aglib.kernel.kconfig()
if not kconfig:
print(
M.warn(
"No kernel configuration found, make sure the kernel was built with CONFIG_IKCONFIG"
)
)
return
options = _hardening_options + _arch_hardening_options.get(pwndbg.aglib.arch.name, [])
for opt in options:
config_name = opt.name

@ -3,29 +3,22 @@ from __future__ import annotations
import argparse
import pwndbg.aglib.kernel
import pwndbg.color.message as M
import pwndbg.commands
from pwndbg.commands import CommandCategory
parser = argparse.ArgumentParser(
description="Outputs the kernel config (requires CONFIG_IKCONFIG)."
)
parser = argparse.ArgumentParser(description="Outputs the kernel config.")
parser.add_argument("config_name", nargs="?", type=str, help="A config name to search for")
parser.add_argument("-l", "--load", type=str, dest="file_path", help="load kernel config file")
@pwndbg.commands.Command(parser, category=CommandCategory.KERNEL)
@pwndbg.commands.OnlyWhenQemuKernel
@pwndbg.commands.OnlyWhenPagingEnabled
def kconfig(config_name=None) -> None:
def kconfig(config_name=None, file_path=None) -> None:
kconfig_ = pwndbg.aglib.kernel.kconfig()
if not kconfig_:
print(
M.warn(
"No kernel configuration found, make sure the kernel was built with CONFIG_IKCONFIG"
)
)
if file_path is not None:
kconfig_.update_with_file(file_path)
return
if config_name:

@ -5,6 +5,10 @@ from collections import UserDict
from typing import Any
from typing import Dict
import pwndbg.aglib
import pwndbg.aglib.kernel
import pwndbg.aglib.symbol
def parse_config(config_text: bytes) -> Dict[str, str]:
res: Dict[str, str] = {}
@ -27,9 +31,29 @@ def config_to_key(name: str) -> str:
class Kconfig(UserDict): # type: ignore[type-arg]
def __init__(self, compressed_config: bytes, *args: Any, **kwargs: Any) -> None:
def __init__(self, compressed_config: bytes | None, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.data = parse_compresed_config(compressed_config)
if compressed_config is not None:
self.data = parse_compresed_config(compressed_config)
return
if self.CONFIG_SLUB_TINY:
self.data["CONFIG_SLUB_TINY"] = "y"
if self.CONFIG_SLUB_CPU_PARTIAL:
self.data["CONFIG_SLUB_CPU_PARTIAL"] = "y"
if self.CONFIG_MEMCG:
self.data["CONFIG_MEMCG"] = "y"
if self.CONFIG_SLAB_FREELIST_RANDOM:
self.data["CONFIG_SLAB_FREELIST_RANDOM"] = "y"
if self.CONFIG_HARDENED_USERCOPY:
self.data["CONFIG_HARDENED_USERCOPY"] = "y"
if self.CONFIG_SLAB_FREELIST_HARDENED:
self.data["CONFIG_SLAB_FREELIST_HARDENED"] = "y"
if self.CONFIG_NUMA:
self.data["CONFIG_NUMA"] = "y"
if self.CONFIG_KASAN_GENERIC:
self.data["CONFIG_KASAN_GENERIC"] = "y"
if self.CONFIG_SMP:
self.data["CONFIG_SMP"] = "y"
def get_key(self, name: str) -> str | None:
# First attempt to lookup the value assuming the user passed in a name
@ -59,3 +83,70 @@ class Kconfig(UserDict): # type: ignore[type-arg]
def __getattr__(self, name: str):
return self.get(name)
@property
def CONFIG_SLUB_TINY(self) -> bool:
if pwndbg.aglib.kernel.krelease() < (6, 2):
return False
return pwndbg.aglib.symbol.lookup_symbol("flushwq") is None
@property
def CONFIG_SLUB_CPU_PARTIAL(self) -> bool:
if pwndbg.aglib.kernel.krelease() < (6, 8):
if pwndbg.aglib.symbol.lookup_symbol("unfreeze_partials") is not None:
return True
return pwndbg.aglib.symbol.lookup_symbol("__unfreeze_partials") is not None
return pwndbg.aglib.symbol.lookup_symbol("__put_partials") is not None
@property
def CONFIG_MEMCG(self) -> bool:
return pwndbg.aglib.symbol.lookup_symbol("kpagecgroup_proc_ops") is not None
@property
def CONFIG_SLAB_FREELIST_RANDOM(self) -> bool:
return pwndbg.aglib.symbol.lookup_symbol("init_cache_random_seq") is not None
@property
def CONFIG_HARDENED_USERCOPY(self) -> bool:
return pwndbg.aglib.symbol.lookup_symbol("__check_heap_object") is not None
@property
def CONFIG_SLAB_FREELIST_HARDENED(self) -> bool:
def __helper(name):
addr = pwndbg.aglib.symbol.lookup_symbol_addr(name)
if addr is not None:
for instr in pwndbg.aglib.nearpc.nearpc(addr, 40):
if "get_random" in instr:
return True
return False
return any(
__helper(name)
for name in (
"kmem_cache_open",
"do_kmem_cache_create",
"__kmem_cache_create",
)
)
@property
def CONFIG_NUMA(self) -> bool:
return pwndbg.aglib.symbol.lookup_symbol("proc_pid_numa_maps_op") is not None
@property
def CONFIG_KASAN_GENERIC(self) -> bool:
# TODO: have a kernel build that tests this
if pwndbg.aglib.kernel.krelease() < (5, 11):
return pwndbg.aglib.symbol.lookup_symbol("kasan_cache_create") is not None
return pwndbg.aglib.symbol.lookup_symbol("__kasan_cache_create") is not None
@property
def CONFIG_SMP(self) -> bool:
return pwndbg.aglib.symbol.lookup_symbol("pcpu_get_vm_areas") is not None
def update_with_file(self, file_path):
for line in open(file_path, "r").read().splitlines():
split = line.split("=")
if len(line) == 0 or line[0] == "#" or len(split) != 2:
continue
self.data[split[0]] = split[1]

@ -42,11 +42,6 @@ def test_gdblib_kernel_krelease():
assert release_str in pwndbg.aglib.kernel.kversion()
@pytest.mark.skipif(not pwndbg.aglib.kernel.has_debug_syms(), reason="test requires debug symbols")
def test_gdblib_kernel_is_kaslr_enabled():
pwndbg.aglib.kernel.is_kaslr_enabled()
@pytest.mark.skipif(not pwndbg.aglib.kernel.has_debug_syms(), reason="test requires debug symbols")
def test_gdblib_kernel_nproc():
# make sure no exception occurs

Loading…
Cancel
Save