-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 NEFARIOUSPLAN-CANONICAL-V1 {"body_md":"## Three macros. Two `//` prefixes. One bug.\n\nRealtek licenses a Wi-Fi reference codebase, the rtl819x Jungle SDK, to the OEMs that build consumer routers, 4G/LTE CPEs, and the silicon-bundle modules embedded inside them. The SDK is closed-source under OEM contract, GPL by license, redistributed to end customers piecemeal by the integrators required to ship source when they ship a binary derived from it. The driver this CVE lives in, `rtl8192cd`, is the family driver behind every Wi-Fi chip in the line: RTL8192C/D/E/F, RTL8188EE, RTL8197F, the variants by SoC and form factor. A complete copy of the source lives in the `vankel/rtl819x-SDK` GitHub redistribution; this post quotes from it.\n\nLines 306 to 308 of `8192cd_cfg.h`:\n\n```c\n//#define _DEBUG_RTL8192CD_\t\t// defined when debug print is used\n#define _IOCTL_DEBUG_CMD_\t\t// defined when read/write register/memory command is used in ioctl\n//#define _AMPSDU_AMSDU_DEBUG_\t\t// defined when debug for ampdu_amsdu\n```\n\nThree macros. Three inline comments stating, in Realtek's own words, what each macro does and that each is a debug feature. Two `//` prefixes. One line without.\n\n`_DEBUG_RTL8192CD_` gates `panic_printk` calls scattered through the driver. The comment says so. Realtek's release engineer commented it out. Production builds emit no debug traces, because at build time the macro is undefined and the `#ifdef _DEBUG_RTL8192CD_` blocks around the prints compile to nothing.\n\n`_AMPSDU_AMSDU_DEBUG_` gates aggregation-protocol debug logging. The comment says so. Realtek's release engineer commented it out. Production builds emit no A-MPDU/A-MSDU traces.\n\n`_IOCTL_DEBUG_CMD_` gates a block of IOCTLs that read and write kernel memory from userspace. The comment next to it admits exactly that, in seven words: \"defined when read/write register/memory command is used in ioctl\". Realtek's release engineer did not comment it out. Production builds compile in the IOCTLs.\n\nThe release-prep discipline that produced the comment markers on the two neighbors did not produce one on the macro between them.\n\n## The handlers do exactly what their names say.\n\nIn `8192cd_ioctl.c`, lines 87 and 88, Realtek defines two command numbers in the Linux wireless private-IOCTL range:\n\n```c\n#define RTL8192CD_IOCTL_WRITE_MEM (SIOCDEVPRIVATE + 0x5) // 0x89f5\n#define RTL8192CD_IOCTL_READ_MEM (SIOCDEVPRIVATE + 0x6) // 0x89f6\n```\n\n`SIOCDEVPRIVATE+N` is the Linux wireless extension's range for vendor-specific commands. Drivers register handlers under this range to expose tuning knobs and chipset diagnostics. A caller reaches them by opening an `AF_INET SOCK_DGRAM` socket and calling `ioctl(fd, 0x89f5, &iwreq)` with the request's `ifr_name` set to the wireless interface. Opening that socket requires no privilege on most kernels and GID `3003` (`inet`) on paranoid kernels.\n\nThe dispatcher's `switch (cmd)` block routes both IOCTLs to handlers inside an `#ifdef _IOCTL_DEBUG_CMD_`:\n\n```c\n#ifdef _IOCTL_DEBUG_CMD_\n case RTL8192CD_IOCTL_WRITE_MEM:\n if ((wrq->u.data.length > sizeof_tmpbuf) ||\n ioctl_copy_from_user(tmpbuf, (void *)wrq->u.data.pointer, wrq->u.data.length))\n break;\n ret = write_mem(priv, tmpbuf);\n break;\n\n case RTL8192CD_IOCTL_READ_MEM:\n if ((wrq->u.data.length > sizeof_tmpbuf) ||\n ioctl_copy_from_user(tmpbuf, (void *)wrq->u.data.pointer, wrq->u.data.length))\n break;\n i = read_mem(priv, tmpbuf);\n if (i >= 0) {\n if ((i > 0) && ioctl_copy_to_user((void *)wrq->u.data.pointer, tmpbuf, i))\n break;\n wrq->u.data.length = i;\n ret = 0;\n }\n break;\n```\n\nWith `_IOCTL_DEBUG_CMD_` defined, both case arms compile. The kernel routes 0x89f5 to `write_mem` and the function below does what its name says:\n\n```c\nstatic int write_mem(struct rtl8192cd_priv *priv, unsigned char *data)\n{\n ...\n start = (unsigned long)_atoi((char *)tmpbuf, 16);\n ...\n len = _atoi((char *)tmpbuf, 16);\n val = (unsigned long)_atoi((char *)data, 16);\n ...\n for (i=0; icomm`. Validate by following the adjacent `cred` pointer and confirming it dereferences to `{usage_refcount, uid=0, gid=0}`.\n3. Bulk-read `0x600` bytes of `init_task` (twelve IOCTLs of 128 bytes each) and detect `task_struct` offsets in place. `comm` is the `\"swapper\"` byte sequence. `cred` is the kernel pointer immediately before it. `tasks` is the only `list_head` whose `next` does not self-reference and whose target dereferences to a printable `comm`. `pid` is the field that reads 0 in `init_task` and reads matching nonzero values in two follow-on tasks.\n4. Walk the task list backward from `init_task` (newest task first via `tasks.prev`) until the running PID is found.\n5. Two `write_mem` IOCTLs against the running task's `cred`: nine 32-bit zeros over `cred + 0x04` (uid, gid, suid, sgid, euid, egid, fsuid, fsgid, securebits), eight 32-bit `0xFFFFFFFF`s over `cred + 0x28` (capability bitmasks for `inheritable`, `permitted`, `effective`, `bounding set`).\n6. `getuid()` returns 0. `execl` for a root shell.\n\nThe exploit's portability is a function of the primitive's directness. There is no shellcode, no ROP, no kernel-symbol assumption, no per-version offset table. `task_struct` layout varies across kernel releases; `kpwn` discovers it at runtime from a single anchor (the `\"swapper\"` byte sequence) and validates each offset against the kernel's own data. The driver and the running kernel are coupled by exactly one thing: the macro on line 307 and the `memcpy` it gates.\n\n## The macro guards eight handlers, not two.\n\nThe `#ifdef _IOCTL_DEBUG_CMD_` block in `8192cd_ioctl.c` covers more than `write_mem` and `read_mem`. Inside that block, between lines 10529 and 10609, the dispatcher routes eight private IOCTLs to handlers gated by the same macro:\n\n```c\n#ifdef _IOCTL_DEBUG_CMD_\n case RTL8192CD_IOCTL_WRITE_REG: /* 0x89f3 */\n case RTL8192CD_IOCTL_READ_REG: /* 0x89f4 */\n case RTL8192CD_IOCTL_WRITE_MEM: /* 0x89f5 */\n case RTL8192CD_IOCTL_READ_MEM: /* 0x89f6 */\n case RTL8192CD_IOCTL_WRITE_BB_REG: /* 0x89fa */\n case RTL8192CD_IOCTL_READ_BB_REG: /* 0x89fb */\n case RTL8192CD_IOCTL_WRITE_RF_REG: /* 0x89fc */\n case RTL8192CD_IOCTL_READ_RF_REG: /* 0x89fd */\n#endif // _IOCTL_DEBUG_CMD_\n```\n\n`WRITE_REG` and `READ_REG` expose the chip's MMIO register space (read or write a hardware register at a caller-supplied offset). `WRITE_BB_REG` and `READ_BB_REG` expose baseband configuration registers. `WRITE_RF_REG` and `READ_RF_REG` expose RF transceiver registers via `PHY_SetRFReg` and `PHY_QueryRFReg`. The hardware-register primitives are not equivalent to the kernel-memory primitives, but they cross the same trust boundary on the same dispatcher, gated by the same single macro, with the same absence of a capability check.\n\nThe CVE record names `write_mem` and `read_mem` because they yield the cleanest LPE chain on a typical embedded kernel. The patch a vendor writes to close CVE-2026-36355 will most likely add a `capable()` check to those two case arms and leave the other six in place. The other six are not the same bug. They are bugs of the same shape, sharing a single gate, written by the same author, shipped under the same release discipline. Anyone patching this CVE is patching one of eight.\n\n## The fix is two characters of comment marker.\n\nThe vczapi WordPress plugin shipped a `check_ajax_referer` call with `//` typed in front of it. We covered that CVE in [the patch is two characters](/posts/vczapi-cve-2026-1368-patch-is-two-characters), the catalog's canonical strict-form exhibit of `todo-that-shipped`: a security control fully written into the source, its only manifestation a working line of code with two characters of comment marker in front of it. The patch was the absence of two characters somebody at some point typed. The line was in the file. The fix did not have to be authored. It had to be uncommented.\n\nCVE-2026-36355 is the mirror form. The patch is the presence of two characters somebody at some point did not type. The line is in the file. The fix does not have to be authored. It has to be commented out.\n\nThe two characters are on the line above:\n\n```c\n//#define _DEBUG_RTL8192CD_\t\t// defined when debug print is used\n```\n\nThe two characters are on the line below:\n\n```c\n//#define _AMPSDU_AMSDU_DEBUG_\t\t// defined when debug for ampdu_amsdu\n```\n\nThey are not on the line between:\n\n```c\n#define _IOCTL_DEBUG_CMD_\t\t// defined when read/write register/memory command is used in ioctl\n```\n\nSome engineer at Realtek sat in a release-prep editor and prepared `8192cd_cfg.h` for a production build. They commented out `_DEBUG_RTL8192CD_` because console prints were noise. They commented out `_AMPSDU_AMSDU_DEBUG_` because A-MPDU/A-MSDU traces had no place in a shipping image. They did not comment out `_IOCTL_DEBUG_CMD_`, because the kernel memory R/W IOCTLs were how the chipset got brought up during bringup, and the release-prep checklist did not name them as something to remove. The same file shipped to every OEM that licensed the SDK. The OEMs did not audit the file. The build process did not enforce the discipline. The macro stayed defined. The IOCTLs stayed compiled in.\n\nThe Realtek Jungle SDK's userspace surface produced CVE-2021-35395, the unauthenticated remote-code-execution bug in the SDK's webserver that received downstream advisories from D-Link, TOTOLINK, Tenda, NEC, I-O DATA, Elecom, Buffalo, Humax, H3C, and IP-COM. The kernel surface of the same SDK is what this CVE covers. Whether any specific product line of those vendors ships this exact driver build is a per-product question that requires reading the per-product GPL release. The macro is the cheap question. Search the redistributed source for `_IOCTL_DEBUG_CMD_` in `8192cd_cfg.h`; if the line is not prefixed with `//`, the device is shipping kernel memory to userspace on every wireless interface the driver runs.\n\nThis is `debug-flag-left-on`: a compile-time feature flag named with a debug convention, unconditionally `#define`d in production headers, with no `#ifdef DEBUG` or build-system gate enforcing the convention. The \"debug only\" contract has two manifestations in the codebase. One is the macro's name. One is an inline comment admitting what the macro does. The runtime sees neither.\n\nPoC: [totekuh/CVE-2026-36355](https://github.com/totekuh/CVE-2026-36355)\n","closing_line":"The patch is two characters. They are on the line above this macro. They are on the line below it. They are not on the middle line.","hook_md":"CVE-2026-36355 is two IOCTLs in a Wi-Fi driver that let any local user read and write kernel memory. The handlers are named `write_mem` and `read_mem`. They are compiled in by a macro named `_IOCTL_DEBUG_CMD_` on line 307 of `8192cd_cfg.h`. The macros on the lines immediately above and below it are also debug macros. Both are commented out. The middle one ships to every OEM that licensed Realtek's rtl819x Jungle SDK.","post_id":328,"slug":"realtek-jungle-sdk-debug-macro-left-on","title":"CVE-2026-36355: Realtek Commented Out Two of Three Debug Macros. The One In The Middle Maps Kernel Memory.","type":"initial","unreadable_sentence":"The patch is the presence of two characters somebody at some point did not type."} -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQRf0htP5+SjynlxywneZjl4jgkQJgUCaiWnMwAKCRDeZjl4jgkQ Jt9yAP9EJ5DaDm1cgo4ILKLSf4UtuMwLPccetfTDV/hpBmAyJQEAoSGeFQu+zpRH g13pw+yLqHA0woR6RO4O2oM4Rsi00gI= =1pjT -----END PGP SIGNATURE-----