Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date: Tue, 16 Jan 2018 15:21:19 +0800 (CST)
From: luo <>
Subject: sound driver Conditional competition

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
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.


At 2018-01-12 09:24:58, "" <> wrote:
>I'll need some details:
>problemtype ideally the CWE identifier ( 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:
Content of type "text/html" skipped

View attachment "competition.c" of type "text/plain" (8367 bytes)

Powered by blists - more mailing lists

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

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.