ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

slub check_object

slub check_object

check_object()是 SLUB 调试一致性检查的核心函数:校验单个对象的所有区域(红区、poison、padding)是否完好,发现破坏则报告并尝试恢复。它综合调用前面讲过的多个辅助函数。

一、函数签名

static int check_object(struct kmem_cache *s, struct slab *slab, void *object, u8 val)
参数含义
s缓存
slab对象所在 slab 页
object要校验的对象指针
val期望的 poison 值(POISON_FREE或POISON_INUSE)

返回int:1 表示对象完好,0 表示发现破坏。

val的含义

val由调用者传入,表示该对象当前应处于什么状态:

  • POISON_FREE(0x6b):对象空闲,应填0x6b。

  • POISON_INUSE(0x5a):对象已分配使用中,应填0x5a。

调用者根据对象是否在 freelist 上判断:

val = test_bit(index, object_map) ? POISON_INUSE : POISON_FREE;

二、典型实现

static int check_object(struct kmem_cache *s, struct slab *slab, void *object, u8 val) { u8 *p = object; u8 *endobject = object + s->object_size; if (s->flags & SLAB_RED_ZONE) { // 左红区 if (!check_bytes_and_report(s, slab, object, "Left Redzone", object - s->red_left_pad, val, s->red_left_pad, true)) return 0; // 右红区 if (!check_bytes_and_report(s, slab, object, "Right Redzone", endobject, val, s->inuse - s->object_size, true)) return 0; } else { // 未开红区,对象首尾的 padding 也应检查 if ((unsigned long *)object != slab->freelist && !check_bytes_and_report(s, slab, object, "Object head", object, val, sizeof(void *), true)) return 0; } if (s->flags & SLAB_POISON) { // 对象数据区后的 poison if (!check_bytes_and_report(s, slab, object, "Poison", p + s->inuse, val, s->size - s->inuse, true)) return 0; } // 对象内部 padding if (!check_pad_bytes(s, slab, p)) return 0; return 1; }

逻辑顺序:

  1. 红区检查(若SLAB_RED_ZONE):

    • 左红区:object - red_left_pad到object,期望val。

    • 右红区:endobject到inuse,期望val。

  2. 对象头检查(无红区时):对象开头sizeof(void *)字节,排除 freelist 头对象。

  3. poison 检查(若SLAB_POISON):p + inuse到s->size,期望val。

  4. padding 检查:check_pad_bytes()校验元数据后到槽位结束的填充。

  5. 全部通过返回 1,任一失败返回 0。

三、各部分解析

1. 左红区

check_bytes_and_report(s, slab, object, "Left Redzone", object - s->red_left_pad, val, s->red_left_pad, true)
  • 起点object - red_left_pad,长度red_left_pad。

  • 期望值val(对象状态对应的 poison)。

  • 左红区被越界写破坏时,这里会检测到。

2. 右红区

check_bytes_and_report(s, slab, object, "Right Redzone", endobject, val, s->inuse - s->object_size, true)
  • 起点object + object_size,长度inuse - object_size。

  • 右红区被越界写破坏时检测到。

3. 无红区时的对象头

if ((unsigned long *)object != slab->freelist && !check_bytes_and_report(s, slab, object, "Object head", object, val, sizeof(void *), true))
  • 无红区时,对象开头可能被 freelist 复用为下一个指针。

  • 若object正是slab->freelist头,其开头是 freepointer,不是 poison,跳过检查。

  • 否则检查对象开头sizeof(void *)字节是否为val。

4. poison 区

check_bytes_and_report(s, slab, object, "Poison", p + s->inuse, val, s->size - s->inuse, true)
  • 对象有效数据后到槽位结束,期望val。

  • 检测对象数据区是否被越界写、是否被释放后改写。

5. padding

if (!check_pad_bytes(s, slab, p)) return 0;
  • 委托check_pad_bytes()校验元数据(track、orig_size 等)后的填充区。

  • 与上面几个check_bytes_and_report互补,覆盖元数据之后的空间。

四、返回值与调用者的处理

返回含义调用者动作
1对象完好继续校验其他对象
0发现破坏(已报告+恢复)计入错误,可能影响 slab 状态判断

validate_slab()里遍历所有对象:

for_each_object(p, s, addr, slab->objects) { u8 val = test_bit(__obj_to_index(s, slab, p), map) ? POISON_INUSE : POISON_FREE; if (!check_object(s, slab, p, val)) return 0; // 该 slab 校验失败 }

五、与相关函数的关系

check_object() ├─ check_bytes_and_report() // 红区、poison、对象头 │ ├─ memchr_inv() │ ├─ slab_bug() │ ├─ print_trailer() │ └─ restore_bytes() │ └─ check_pad_bytes() // 对象内部 padding └─ check_bytes_and_report()
函数检查区域期望值
check_bytes_and_report(左红区)对象前val
check_bytes_and_report(右红区)对象后val
check_bytes_and_report(对象头)对象开头val
check_bytes_and_report(poison)数据区后val
check_pad_bytes元数据后POISON_INUSE

六、val与对象状态

check_object()的关键在于val的正确选择:

  • 对象在 freelist 上(空闲):val = POISON_FREE,期望空闲区域填0x6b。

  • 对象已分配(使用中):val = POISON_INUSE,期望使用区域填0x5a。

如果期望值与实际不符,说明:

  • 空闲对象被改写 → use-after-free。

  • 已分配对象的 poison 被覆盖 → 未初始化使用或越界写。

  • 红区被破坏 → 越界写。

七、与前面知识的呼应

前面讲过这里体现
check_bytes_and_report()核心校验子函数
check_pad_bytes()对象内部 padding 检查
restore_bytes()破坏恢复
print_trailer()现场打印
SLAB_RED_ZONE/SLAB_POISON控制检查哪些区域
red_left_pad/inuse/size区域边界计算
POISON_FREE/POISON_INUSEval取值
object_map/__obj_to_index判断对象是否已分配,决定val
for_each_object()遍历对象调用check_object

八、一句话总结

check_object(s, slab, object, val)校验单个对象的左红区、右红区、对象头、poison 区、内部 padding是否都等于期望的val(空闲POISON_FREE/ 使用POISON_INUSE):逐段调用check_bytes_and_report()和check_pad_bytes(),发现破坏就报告、打印现场并恢复。返回 1 表示对象完好,0 表示发现并修复了破坏。它是validate_slab()的核心子函数,是 SLUB 调试一致性检查的落地点。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进