Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

crypto: atmel-sha - fix a race between the 'done' tasklet and the crypto client

The 'done' tasklet handler used to check the 'BUSY' flag to either
finalize the processing of a crypto request which had just completed or
manage the crypto queue to start the next crypto request.

On request R1 completion, the driver calls atmel_sha_finish_req(), which:
1 - clears the 'BUSY' flag since the hardware is no longer used and is
ready again to process new crypto requests.
2 - notifies the above layer (the client) about the completion of the
asynchronous crypto request R1 by calling its base.complete()
callback.
3 - schedules the 'done' task to check the crypto queue and start to
process the next crypto request (the 'BUSY' flag is supposed to be
cleared at that moment) if such a pending request exists.

However step 2 might wake the client up so it can now ask our driver to
process a new crypto request R2. This request is enqueued by calling the
atmel_sha_handle_queue() function, which sets the 'BUSY' flags then
starts to process R2.

If the 'done' tasklet, scheduled by step 3, runs just after, it would see
that the 'BUSY' flag is set then understand that R2 has just completed,
which is wrong!

So the state of 'BUSY' flag is not a proper way to detect and handle
crypto request completion.

This patch fixes this race condition by using two different tasklets, one
to handle the crypto request completion events, the other to manage the
crypto queue if needed.

Signed-off-by: Cyrille Pitchen <cyrille.pitchen@atmel.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

authored by

Cyrille Pitchen and committed by
Herbert Xu
f56809c3 1900c583

+13 -6
+13 -6
drivers/crypto/atmel-sha.c
··· 122 122 spinlock_t lock; 123 123 int err; 124 124 struct tasklet_struct done_task; 125 + struct tasklet_struct queue_task; 125 126 126 127 unsigned long flags; 127 128 struct crypto_queue queue; ··· 789 788 req->base.complete(&req->base, err); 790 789 791 790 /* handle new request */ 792 - tasklet_schedule(&dd->done_task); 791 + tasklet_schedule(&dd->queue_task); 793 792 } 794 793 795 794 static int atmel_sha_hw_init(struct atmel_sha_dev *dd) ··· 1102 1101 }, 1103 1102 }; 1104 1103 1104 + static void atmel_sha_queue_task(unsigned long data) 1105 + { 1106 + struct atmel_sha_dev *dd = (struct atmel_sha_dev *)data; 1107 + 1108 + atmel_sha_handle_queue(dd, NULL); 1109 + } 1110 + 1105 1111 static void atmel_sha_done_task(unsigned long data) 1106 1112 { 1107 1113 struct atmel_sha_dev *dd = (struct atmel_sha_dev *)data; 1108 1114 int err = 0; 1109 - 1110 - if (!(SHA_FLAGS_BUSY & dd->flags)) { 1111 - atmel_sha_handle_queue(dd, NULL); 1112 - return; 1113 - } 1114 1115 1115 1116 if (SHA_FLAGS_CPU & dd->flags) { 1116 1117 if (SHA_FLAGS_OUTPUT_READY & dd->flags) { ··· 1370 1367 1371 1368 tasklet_init(&sha_dd->done_task, atmel_sha_done_task, 1372 1369 (unsigned long)sha_dd); 1370 + tasklet_init(&sha_dd->queue_task, atmel_sha_queue_task, 1371 + (unsigned long)sha_dd); 1373 1372 1374 1373 crypto_init_queue(&sha_dd->queue, ATMEL_SHA_QUEUE_LENGTH); 1375 1374 ··· 1464 1459 atmel_sha_dma_cleanup(sha_dd); 1465 1460 err_sha_dma: 1466 1461 res_err: 1462 + tasklet_kill(&sha_dd->queue_task); 1467 1463 tasklet_kill(&sha_dd->done_task); 1468 1464 sha_dd_err: 1469 1465 dev_err(dev, "initialization failed.\n"); ··· 1485 1479 1486 1480 atmel_sha_unregister_algs(sha_dd); 1487 1481 1482 + tasklet_kill(&sha_dd->queue_task); 1488 1483 tasklet_kill(&sha_dd->done_task); 1489 1484 1490 1485 if (sha_dd->caps.has_dma)