# Collected Data — Phase 1 Facts Dump Report: [69e93024.a00a0220.17a17.0031.GAE@google.com](https://lore.kernel.org/r/69e93024.a00a0220.17a17.0031.GAE@google.com) Subject: [syzbot] [i2c?] WARNING: refcount bug in i2c_get_adapter (2) Collected: Phase 1 complete --- ## Debug symbols - **vmlinux path**: `oops-workdir/syzbot/vmlinux-e753c16c` (absolute: `/sdb1/arjan/git/oops-skill/oops-workdir/syzbot/vmlinux-e753c16c`) - Download: fetcher agent downloaded and decompressed from `https://storage.googleapis.com/syzbot-assets/6ef912fb95e2/vmlinux-e753c16c.xz`; size 1.6 GB - Kernel version string: syzbot kernel at HEAD commit `e753c16cb3dd` (Merge tag 'spi-fix-v7.0-rc7') - nm symbol table: 385,550 text symbols loaded by `backtrace_resolve.py` ## Source tree - **SOURCEDIR**: `oops-workdir/linux` (absolute: `/sdb1/arjan/git/oops-skill/oops-workdir/linux`) - HEAD commit: `e753c16cb3dd` — checked out by fetcher agent; verified by semcode index refresh - Semcode index: refreshed at `oops-workdir/linux/.semcode.db` ## Resolved addresses Full table from `backtrace_resolve.py` (all entries including those not in report.md): | Index | Function | Offset | Address (formatted) | Source (primary) | Source (inlined frames) | function_type | |-------|----------|--------|---------------------|------------------|-------------------------|---------------| | 0 | `refcount_warn_saturate` | 0x9f | `0xffffffff849eaa6f (0xffffffff849ea9d0 + 0x9f)` | `lib/refcount.c:0` (line 0 = DWARF sparse; WARNING header gives :25) | — | normal | | 1 | `kobject_get` | 0xfa | `0xffffffff8b1ab49a (0xffffffff8b1ab3a0 + 0xfa)` | `include/linux/refcount.h:0` | `__refcount_inc`→refcount.h:366; `refcount_inc`→refcount.h:383; `kref_get`→kref.h:45; `kobject_get`→kobject.c:643 | normal | | 2 | `i2c_get_adapter` | 0x6d | `0xffffffff8742a42d (0xffffffff8742a3c0 + 0x6d)` | `drivers/i2c/i2c-core-base.c:2612` | — | normal | | 3 | `i2cdev_open` | 0x48 | `0xffffffff8743ec48 (0xffffffff8743ec00 + 0x48)` | `drivers/i2c/i2c-dev.c:603` | — | normal | | 4 | `chrdev_open` | 0x4d0 | `0xffffffff82392340 (0xffffffff82391e70 + 0x4d0)` | `fs/char_dev.c:411` | — | normal | | 5 | `do_dentry_open` | 0x83d | `0xffffffff8236e11d (0xffffffff8236d8e0 + 0x83d)` | `fs/open.c:949` | — | normal | | 6 | `vfs_open` | 0x3b | `0xffffffff8236edbb (0xffffffff8236ed80 + 0x3b)` | `fs/open.c:1081` | — | normal | | 7 | `path_openat` | 0x2e43 | `0xffffffff823bc223 (0xffffffff823b93e0 + 0x2e43)` | `fs/namei.c:4677` | `path_openat`→namei.c:4836 | normal | | 8 | `do_file_open` | 0x23e | `0xffffffff823b916e (0xffffffff823b8f30 + 0x23e)` | `fs/namei.c:0` | — | normal | | 9 | `do_sys_openat2` | 0x113 | `0xffffffff823701a3 (0xffffffff82370090 + 0x113)` | `fs/open.c:0` | — | normal | | 10 | `__x64_sys_openat` | 0x138 | `0xffffffff82370698 (0xffffffff82370560 + 0x138)` | `fs/open.c:1372` | `__do_sys_openat`→open.c:1388; `__se_sys_openat`→open.c:1383; `__x64_sys_openat`→open.c:1383 | normal | | 11 | `do_syscall_64` | 0x14d | `0xffffffff8b23c1cd (0xffffffff8b23c080 + 0x14d)` | `arch/x86/entry/syscall_64.c:63` | `do_syscall_64`→syscall_64.c:94 | normal | | 12 | `entry_SYSCALL_64_after_hwframe` | 0x77 | `0xffffffff81000130 (0xffffffff810000b9 + 0x77)` | `arch/x86/entry/entry_64.S:121` | — | normal | Note: index 0 primary source line is 0 (DWARF sparse for warning path); actual site is lib/refcount.c:25 from the WARNING header line directly. ## Code bytes Raw bytes from the `Code:` field of the oops: ``` eb 66 85 db 74 3e 83 fb 01 75 4c e8 bb d6 25 fd 48 8d 3d 84 01 d6 0a 67 48 0f b9 3a eb 4a e8 a8 d6 25 fd 48 8d 3d 81 01 d6 0a <67> 48 0f b9 3a eb 37 e8 95 d6 25 fd 48 8d 3d 7e 01 d6 0a 67 48 0f ``` `<67>` marks the crash instruction start. Decoded crash instruction: - `67` = address-size override prefix - `48` = REX.W prefix - `0f b9 3a` = `UD1 edi, [rdx]` — the `UD1` opcode used by Linux's WARN mechanism as a static-call trampoline (`__SCT__WARN_trap`) Disasm window (from `backtrace_resolve.py`, index 0): ``` ffffffff849eaa32: jne refcount_warn_saturate+0xcc ffffffff849eaa34: call __sanitizer_cov_trace_pc ffffffff849eaa39: lea 0xad60190(%rip),%rdi # bug-table entry (REFCOUNT_ADD_NOT_ZERO_OVF) ffffffff849eaa40: call __SCT__WARN_trap ffffffff849eaa45: jmp refcount_warn_saturate+0xdd ffffffff849eaa47: test %ebx,%ebx ffffffff849eaa49: je refcount_warn_saturate+0xb9 ffffffff849eaa4b: cmp $0x1,%ebx ffffffff849eaa4e: jne refcount_warn_saturate+0xcc ffffffff849eaa50: call __sanitizer_cov_trace_pc ffffffff849eaa55: lea 0xad60184(%rip),%rdi # bug-table entry (REFCOUNT_ADD_OVF) ffffffff849eaa5c: call __SCT__WARN_trap ffffffff849eaa61: jmp refcount_warn_saturate+0xdd ffffffff849eaa63: call __sanitizer_cov_trace_pc ffffffff849eaa68: lea 0xad60181(%rip),%rdi # bug-table entry (REFCOUNT_ADD_UAF) ffffffff849eaa6f: call __SCT__WARN_trap <<< crash (UD1 static-call trampoline) ffffffff849eaa74: jmp refcount_warn_saturate+0xdd ffffffff849eaa76: call __sanitizer_cov_trace_pc ffffffff849eaa7b: lea 0xad6017e(%rip),%rdi # bug-table entry (REFCOUNT_SUB_UAF) ffffffff849eaa82: call __SCT__WARN_trap ffffffff849eaa87: jmp refcount_warn_saturate+0xdd ``` Factual observation: RBX = 0x2 = `REFCOUNT_ADD_UAF` (enum value 2 in `enum refcount_saturation_type`). The switch dispatches to the `REFCOUNT_ADD_UAF` case, confirming the warning text. ## Register annotations | Register | Value | Decoded meaning | |----------|-------|-----------------| | RIP | `0xffffffff849eaa6f` | `refcount_warn_saturate+0x9f` — call to `__SCT__WARN_trap` | | RSP | `0xffffc9001ca9f6d8` | Kernel stack (vmalloc/stack area `0xffffc9...`) | | EFLAGS | `0x00010283` | RF=1 (resume flag, expected after trap), IF=1 (interrupts enabled), CF=1, PF=0 | | RAX | `0xffffffff849eaa68` | = RIP − 7: address of the preceding `lea` instruction loading the REFCOUNT_ADD_UAF bug-table string | | RBX | `0x0000000000000002` | = 2 = `REFCOUNT_ADD_UAF` — the saturation type passed to `refcount_warn_saturate` | | RDI | `0xffffffff8f74abf0` | Pointer to bug-table entry `"refcount_t: addition on 0; use-after-free"` (in `__start___bug_table+0x83cf0`) | | R13 | `0xdffffc0000000000` | KASAN shadow base offset (common pattern in syzbot runs) | | R14 | `0xffff88803b531188` | Kernel heap address (direct-map range); likely `struct kobject *` or `struct device *` of the freed adapter | | R15 | `0xdffffc0000000000` | KASAN shadow base offset | ## Full source excerpts ### `lib/refcount.c` — `refcount_warn_saturate` (lines 1–37) ```c // SPDX-License-Identifier: GPL-2.0 /* * Out-of-line refcount functions. */ #include #include #include #include #define REFCOUNT_WARN(str) WARN_ONCE(1, "refcount_t: " str ".\n") void refcount_warn_saturate(refcount_t *r, enum refcount_saturation_type t) { refcount_set(r, REFCOUNT_SATURATED); switch (t) { case REFCOUNT_ADD_NOT_ZERO_OVF: REFCOUNT_WARN("saturated; leaking memory"); break; case REFCOUNT_ADD_OVF: REFCOUNT_WARN("saturated; leaking memory"); break; case REFCOUNT_ADD_UAF: REFCOUNT_WARN("addition on 0; use-after-free"); break; case REFCOUNT_SUB_UAF: REFCOUNT_WARN("underflow; use-after-free"); break; case REFCOUNT_DEC_LEAK: REFCOUNT_WARN("decrement hit 0; leaking memory"); break; default: REFCOUNT_WARN("unknown saturation event!?"); } } EXPORT_SYMBOL(refcount_warn_saturate); ``` No `CONFIG_` guard — fires in all builds. ### `include/linux/refcount.h` — inline chain (lines 364–384) ```c static inline void __refcount_inc(refcount_t *r, int *oldp) { __refcount_add(1, r, oldp); /* line 366 */ } static inline void refcount_inc(refcount_t *r) /* line 381 */ { __refcount_inc(r, NULL); /* line 383 */ } ``` `refcount_inc` documentation (from header): "Will WARN if the refcount is 0, as this represents a possible use-after-free condition." ### `lib/kobject.c` — `kobject_get` (lines 636–647) ```c struct kobject *kobject_get(struct kobject *kobj) { if (kobj) { if (!kobj->state_initialized) WARN(1, KERN_WARNING "kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n", kobject_name(kobj), kobj); kref_get(&kobj->kref); /* line 643 — unconditional increment */ } return kobj; } ``` Adjacent function: `kobject_get_unless_zero` (line 649) uses `kref_get_unless_zero` which does check for zero first. ### `drivers/base/core.c` — `get_device` (lines 3784–3788) ```c struct device *get_device(struct device *dev) { return dev ? kobj_to_dev(kobject_get(&dev->kobj)) : NULL; } ``` Direct call chain: `i2c_get_adapter:2612` → `get_device` → `kobject_get` → `kref_get` → `refcount_inc` → WARN. ### `drivers/i2c/i2c-core-base.c` — `i2c_get_adapter` (lines 2602–2620) ```c struct i2c_adapter *i2c_get_adapter(int nr) { struct i2c_adapter *adapter; mutex_lock(&core_lock); adapter = idr_find(&i2c_adapter_idr, nr); if (!adapter) goto exit; if (try_module_get(adapter->owner)) get_device(&adapter->dev); /* line 2612 — crash site caller */ else adapter = NULL; exit: mutex_unlock(&core_lock); return adapter; } EXPORT_SYMBOL(i2c_get_adapter); ``` ### `drivers/i2c/i2c-dev.c` — `i2cdev_open` (lines 597–618) ```c static int i2cdev_open(struct inode *inode, struct file *file) { unsigned int minor = iminor(inode); struct i2c_client *client; struct i2c_adapter *adap; adap = i2c_get_adapter(minor); /* line 603 */ if (!adap) return -ENODEV; client = kzalloc_obj(*client); if (!client) { i2c_put_adapter(adap); return -ENOMEM; } snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr); client->adapter = adap; file->private_data = client; return 0; } ``` ## Factual notes - WARNING type: runtime consistency check — `WARN_ONCE` inside `refcount_warn_saturate()`, triggered by `REFCOUNT_ADD_UAF` case (RBX = 0x2). This fires when `__refcount_add` detects the old value was 0 before increment. - RBX = 2 matches `REFCOUNT_ADD_UAF = 2` in `enum refcount_saturation_type` in `include/linux/refcount.h`. - The saturation type `REFCOUNT_ADD_UAF` is set when `__refcount_add` sees `old == 0` (object already freed/dropped to zero) and the caller still attempts an increment. - `i2c_get_adapter` found a non-NULL adapter via `idr_find` (adapter still in the IDR) and `try_module_get` succeeded, but `adapter->dev.kobj.kref.refcount` was already 0. - The i2c adapter's struct device (and kobject) refcount was 0 at the time `get_device` was called from `i2c_get_adapter`, meaning the device had already been released. - Taint flag `L` (softlockup) is present but the crash is a WARNING, not a lockup; the softlockup may be a side effect or a separate event. - `core_lock` mutex is held by `i2c_get_adapter` during the `get_device` call (mutex_lock at line 2606, unlock at 2617). - Recent git log for `i2c_get_adapter` includes commit `611e12ea0f121` "i2c: core: manage i2c bus device refcount in i2c_[get|put]_adapter" — this commit introduced the current refcount management pattern in these functions. - Recent git log for `i2cdev_open` includes commit `5136ed4fcb05c` "i2c-dev: don't get i2c adapter via i2c_dev" — relevant historical change to how the adapter is retrieved. ## Git blame hashes for key entries ### `i2c_get_adapter` (drivers/i2c/i2c-core-base.c) - `1da177e4c3f41524e886b7f1b8a0c1fc7321cac2` — initial import - `611e12ea0f121a31d9e9c4ce2a18a77abc2f28d6` — "i2c: core: manage i2c bus device refcount in i2c_[get|put]_adapter" - `a0920e10438e9fe8b22aba607083347c84458ed8` - `c1d084759c95ecd0ef08274654a1f6c4f343cdcd` - `caada32afe0d181b1dc36ab3fc29628582776e09` - `d735b34db30b7891ff76b552d18ecb0ce04a2bc2` - `438d6c2c015cf63bf7e9bdc2033d435433ac8455` ### `i2cdev_open` (drivers/i2c/i2c-dev.c) - `1da177e4c3f41524e886b7f1b8a0c1fc7321cac2` — initial import - `22f76e744dc41096987c6df8270b5c249511cde5` - `5136ed4fcb05c` — "i2c-dev: don't get i2c adapter via i2c_dev" - `7d5cb45655f2e9e37ef75d18f50c0072ef14a38b` - `907135aaa0cc120a347222c8f274ecc5ca0db641` - `9669f54194b4df34c96478d696d9ba2b977545f5` - `bf4afc53b77aeaa48b5409da5c8da6bb4eff7f43`