|
| 1 | +// This file is part of KASLD - https://github.com/bcoles/kasld |
| 2 | +// |
| 3 | +// Read physical MMIO base addresses from framebuffer and serial device ioctls. |
| 4 | +// A fallback for /proc/iomem (CAP_SYS_ADMIN-masked) and sysfs PCI resources |
| 5 | +// (PCI-only): framebuffer and on-chip serial are typically *platform* devices, |
| 6 | +// whose MMIO windows neither source exposes. |
| 7 | +// |
| 8 | +// /dev/fb* FBIOGET_FSCREENINFO -> struct fb_fix_screeninfo |
| 9 | +// .smem_start physical frame-buffer base (+ .smem_len) |
| 10 | +// .mmio_start device MMIO register base (+ .mmio_len) |
| 11 | +// /dev/ttyS*, /dev/ttyAMA* |
| 12 | +// TIOCGSERIAL -> struct serial_struct |
| 13 | +// .iomem_base = uport->mapbase, the UART's physical MMIO base |
| 14 | +// (0 for legacy port-I/O 8250 — x86 COM ports) |
| 15 | +// |
| 16 | +// Both GET paths copy the raw physical address (not %p-hashed, no |
| 17 | +// kptr_restrict) and are *ungated*: fbmem.c's FBIOGET_FSCREENINFO has no |
| 18 | +// capability check, and serial_core.c's uart_get_info() gates only the SET |
| 19 | +// path, not the GET. The only barrier is opening the device node (video / |
| 20 | +// dialout group, or root). |
| 21 | +// |
| 22 | +// Leak primitive: |
| 23 | +// Data leaked: physical MMIO base addresses (framebuffer / UART) |
| 24 | +// Kernel subsystem: drivers/video/fbdev (FBIOGET_FSCREENINFO), |
| 25 | +// drivers/tty/serial (TIOCGSERIAL / uart_get_info) |
| 26 | +// Address type: physical (MMIO) |
| 27 | +// Method: parsed (device ioctl) |
| 28 | +// Status: unfixed (information exposure by design) |
| 29 | +// Access check: none beyond device-node permissions (no CAP / kptr gate) |
| 30 | +// |
| 31 | +// Engine fit: emitted as REGION_MMIO PHYS windows (range when a length is |
| 32 | +// known, else a base), which mmio_floor_phys_ceiling uses to ceiling |
| 33 | +// Q_PHYS_TEXT_BASE (the image must sit in DRAM below the lowest MMIO above it). |
| 34 | +// Decoupled arches only; loose, and additive mainly when /proc/iomem is masked. |
| 35 | +// |
| 36 | +// Mitigations: |
| 37 | +// CONFIG_FB=n / CONFIG_SERIAL_CORE=n remove the respective source; tightening |
| 38 | +// device-node group permissions removes the access. No runtime sysctl gate. |
| 39 | +// --- |
| 40 | +// <bcoles@gmail.com> |
| 41 | + |
| 42 | +#define _GNU_SOURCE |
| 43 | +#include "include/kasld/api.h" |
| 44 | +#include "include/kasld/cli.h" |
| 45 | + |
| 46 | +#include <fcntl.h> |
| 47 | +#include <linux/fb.h> |
| 48 | +#include <linux/serial.h> |
| 49 | +#include <stdint.h> |
| 50 | +#include <stdio.h> |
| 51 | +#include <string.h> |
| 52 | +#include <sys/ioctl.h> |
| 53 | +#include <unistd.h> |
| 54 | + |
| 55 | +KASLD_EXPLAIN( |
| 56 | + "Queries framebuffer (FBIOGET_FSCREENINFO -> smem_start/mmio_start) and " |
| 57 | + "serial (TIOCGSERIAL -> iomem_base) device ioctls for physical MMIO base " |
| 58 | + "addresses. Both GET paths are ungated (no capability or kptr_restrict " |
| 59 | + "check); access needs only the device node (video/dialout group). MMIO " |
| 60 | + "bases ceiling the physical kernel base on decoupled arches — a fallback " |
| 61 | + "for when /proc/iomem is masked and for platform (non-PCI) devices."); |
| 62 | + |
| 63 | +KASLD_META("method:parsed\n" |
| 64 | + "phase:inference\n" |
| 65 | + "addr:physical\n"); |
| 66 | + |
| 67 | +/* Emit one MMIO window as a PHYS landmark: a range when a length is known, else |
| 68 | + * a base (lo edge). Both set HAS_LO, which mmio_floor_phys_ceiling consumes. |
| 69 | + * Returns 1 if emitted, 0 for a zero (absent) base. */ |
| 70 | +static int emit_mmio(unsigned long start, unsigned long len, const char *name) { |
| 71 | + unsigned long hi; |
| 72 | + if (!start) |
| 73 | + return 0; |
| 74 | + if (len && !kasld_add_ovf(start, len - 1, &hi)) |
| 75 | + kasld_result_range(KASLD_TYPE_PHYS, REGION_MMIO, start, hi, name, |
| 76 | + CONF_PARSED); |
| 77 | + else |
| 78 | + kasld_result_base(KASLD_TYPE_PHYS, REGION_MMIO, start, name, CONF_PARSED); |
| 79 | + return 1; |
| 80 | +} |
| 81 | + |
| 82 | +/* FBIOGET_FSCREENINFO returns fb_fix_screeninfo with the physical frame-buffer |
| 83 | + * (smem_start) and device-MMIO (mmio_start) bases. */ |
| 84 | +static int scan_framebuffers(void) { |
| 85 | + int found = 0; |
| 86 | + for (int i = 0; i < 8; i++) { |
| 87 | + char dev[32]; |
| 88 | + snprintf(dev, sizeof(dev), "/dev/fb%d", i); |
| 89 | + int fd = kasld_open(dev, O_RDONLY | O_NONBLOCK | O_NOCTTY); |
| 90 | + if (fd < 0) |
| 91 | + continue; |
| 92 | + struct fb_fix_screeninfo fix; |
| 93 | + memset(&fix, 0, sizeof(fix)); |
| 94 | + if (ioctl(fd, FBIOGET_FSCREENINFO, &fix) == 0) { |
| 95 | + found += emit_mmio(fix.smem_start, fix.smem_len, "framebuffer"); |
| 96 | + found += emit_mmio(fix.mmio_start, fix.mmio_len, "fb_mmio"); |
| 97 | + } |
| 98 | + close(fd); |
| 99 | + } |
| 100 | + return found; |
| 101 | +} |
| 102 | + |
| 103 | +/* TIOCGSERIAL returns serial_struct.iomem_base = uport->mapbase, the physical |
| 104 | + * MMIO base of an MMIO-mapped UART (0 for legacy port-I/O 8250). */ |
| 105 | +static int scan_serial(void) { |
| 106 | + static const char *const fmts[] = {"/dev/ttyS%d", "/dev/ttyAMA%d", NULL}; |
| 107 | + int found = 0; |
| 108 | + for (int t = 0; fmts[t]; t++) { |
| 109 | + for (int i = 0; i < 4; i++) { |
| 110 | + char dev[32]; |
| 111 | + snprintf(dev, sizeof(dev), fmts[t], i); |
| 112 | + int fd = kasld_open(dev, O_RDONLY | O_NONBLOCK | O_NOCTTY); |
| 113 | + if (fd < 0) |
| 114 | + continue; |
| 115 | + struct serial_struct ss; |
| 116 | + memset(&ss, 0, sizeof(ss)); |
| 117 | + if (ioctl(fd, TIOCGSERIAL, &ss) == 0) |
| 118 | + found += emit_mmio((unsigned long)(uintptr_t)ss.iomem_base, 0, |
| 119 | + "serial_mmio"); |
| 120 | + close(fd); |
| 121 | + } |
| 122 | + } |
| 123 | + return found; |
| 124 | +} |
| 125 | + |
| 126 | +int main(int argc, char **argv) { |
| 127 | + kasld_cli(argc, argv); |
| 128 | + |
| 129 | + kasld_info( |
| 130 | + "querying framebuffer / serial ioctls for physical MMIO bases ..."); |
| 131 | + int found = scan_framebuffers() + scan_serial(); |
| 132 | + |
| 133 | + if (!found) { |
| 134 | + kasld_err("no MMIO bases from fb/serial ioctls " |
| 135 | + "(no accessible device, or port-I/O only)"); |
| 136 | + return 0; |
| 137 | + } |
| 138 | + kasld_found("leaked %d physical MMIO base(s) via device ioctls", found); |
| 139 | + return 0; |
| 140 | +} |
0 commit comments