Security Linux Kernel Crypto Subsystem of Linux Kernel - Asynchronous Request Handling Mechanism

由於在 crypto subsystem 中預期多個 crypto request 可以同時向同一個 crypto engine 發出請求 ,因此 crypto engine driver 必須實作對應機制,使其有能力能應付此情況。此外,結合在前一節有提到 crypto subsystem 的 asynchronous crypto API 流程,較常見的實作方式就是 crypto queue 搭配 worker,額外開一個 kernel thread 來與 crypto engine 進行溝通,並讓 crypto request 按照 FIFO 順序處理,而本文主要針對此設計方式,說明整個運作流程和細節。

Overview

條件

  1. 假設 hardware crypto engine 一次只能處理一個 request,將 request 根據需求設置好 register 之後,啟動 crypto engine 進行運算,運算完結果後才能換下一個 request。
  2. 當運算出結果後,crypto engine 會舉起 status interrupt,通知外部已運算完成。
  3. 多個 Request 有可能同時對 hardware crypto engine 發出請求。
  4. 一個完整的 crypto request 流程包含三個 API call: Init → Update → Final, Final 結果回傳後,則此 crypto request 將會被釋放,不再使用。

想法

  1. 建立一個全域的 crypto request list,將進來的 request 依序排到 list 當中。
  2. 建立一個 worker (kernel thread) 和對應的 work queue 來與 hardware crypto engine 進行溝通。worker 的任務除了從 crypto request list 中取出 request 來處理之外,也可能會包含 crypto engine 的初始化和資源釋放等工作。
  3. 註冊 interrupt handler,當 status interrupt 舉起時,呼叫 user 自定義的 completion callback function 來完成最後的流程。如果當前是執行最後的 final API call 且 request 有自定義的 resource 需要被釋放,則會在呼叫完 callback function 後執行。

實作

Linux kernel version: v5.17.3

Crypto Queue

Linux kernel 有實作通用型的 crypto queue structure 以及對應的操作 API:

 1struct crypto_queue {
 2	struct list_head list;
 3	struct list_head *backlog;
 4
 5	unsigned int qlen;
 6	unsigned int max_qlen;
 7};
 8
 9void crypto_init_queue(struct crypto_queue *queue, unsigned int max_qlen);
10
11int crypto_enqueue_request(struct crypto_queue *queue, struct crypto_async_request *request);
12void crypto_enqueue_request_head(struct crypto_queue *queue, struct crypto_async_request *request);
13
14struct crypto_async_request *crypto_dequeue_request(struct crypto_queue *queue);
15
16static inline unsigned int crypto_queue_len(struct crypto_queue *queue);

在大多數情況下,我們可以直接利用此 structure 來實現 crypto request list,不過根據我們上述的場景,request list 可能會被多個 request 同時操作,因此要再加上 lock 機制保護。

 1struct cherie_crypto_engine {
 2    struct device  *dev;
 3
 4    struct crypto_queue  	  queue;
 5    struct kthread_worker   *kworker;
 6	  struct kthread_work     do_requests;
 7    spinlock_t		          queue_lock;
 8
 9    struct crypto_async_request *current_req;
10};
11
12static int cherie_request_enqueue(struct ahash_request *req)
13{
14	int ret;
15	unsigned long flags;
16	struct cherie_crypto_engine *engine = get_engine();
17
18	spin_lock_irqsave(&engine->queue_lock, flags);
19	ret = crypto_enqueue_request(&engine->queue, &req->base);
20	spin_unlock_irqrestore(&engine->queue_lock, flags);
21	return ret;
22}

Worker & Worker Queue

Worker 是唯一可以操作 crypto engine 的 kernel thread,以確保 crypto engine 一次只會執行一個任務。同樣地,我們也利用 Linux kernel 本身提供的 worker API 來實現:

1struct kthread_worker *kthread_create_worker(unsigned int flags, const char namefmt[], ...);
2bool kthread_queue_work(struct kthread_worker *worker, struct kthread_work *work);

至於 work 可能會包含幾個項目:

  1. crypto engine 初始化。
  2. 從 crypto request list 取出 request,並依據 request 的資訊進行 crypto engine 相關 register 的讀寫操作。
  3. crypto engine 資源的釋放。(例如當前沒有 request 要處理時,可以先釋放相關資源)
 1static void cherie_work(struct kthread_work *work)
 2{
 3	unsigned long flags;
 4  struct cherie_request_state *state;
 5	struct ahash_request *req;
 6	struct crypto_async_request *async_req;
 7	struct cherie_crypto_engine *engine = get_engine();
 8	
 9	spin_lock_irqsave(&engine->queue_lock, flags);
10
11	if (!engine->initialize)
12  {
13     // do initialization
14  }
15
16	// we can't fetch the next request if the current request isn't done.
17	if (engine->current_req)
18	{
19		spin_unlock_irqrestore(&engine->queue_lock, flags);
20		return;
21	}
22
23	async_req = crypto_dequeue_request(&engine->queue);
24	spin_unlock_irqrestore(&engine->queue_lock, flags);
25
26	if (!async_req)
27		return;
28
29	req = ahash_request_cast(async_req);
30	state = ahash_request_ctx(req);
31
32	switch (state->algo_op)
33	{
34	case ALGO_UPDATE:
35		cherie_do_request_update(req);
36		break;
37	case ALGO_FINAL:
38		cherie_do_request_final(req);
39		break;
40	default:
41		break;
42	}
43}

Status Interrupt Handling

由於是 asynchronous request 機制,因此在 crypto engine 計算完成、舉起 status interrupt signal 之後,透過 bottom half 方式呼叫 user 定義的 completion callback function 來結束此階段的 API call。

 1static irqreturn_t cherie_crypto_engine_irq_thread_fn(int irq, void *arg)
 2{
 3	unsigned long flags;
 4	struct cherie_crypto_engine *engine = get_engine();
 5
 6	spin_lock_irqsave(&engine->queue_lock, flags);
 7
 8	if (engine->current_req)
 9	{
10		engine->current_req->complete(engine->current_req, 0);
11		engine->current_req = NULL;
12	}
13	spin_unlock_irqrestore(&engine->queue_lock, flags);
14	// add a work to process the next request
15	kthread_queue_work(ctx->kworker, &ctx->do_requests);
16
17	return IRQ_HANDLED;
18}
19
20static int cherie_crypto_engine_probe(struct platform_device *pdev)
21{
22  int irq, ret;
23  irq = platform_get_irq(pdev, 0);
24	if (irq < 0)
25		return irq;
26
27	ret = devm_request_threaded_irq(dev, irq, cherie_crypto_engine_irq_handler,
28					cherie_crypto_engine_irq_thread_fn, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
29					dev_name(dev), ctx);
30}

Implement Crypto API

最後實作 Crypto API ,也就是在 overview 中有提到的 transformation implementation。

通常來說,asynchronous request 回傳的 status code 有三種:0 代表成功、-EINPROGRESS 代表正在處理中、剩下的代表其他 error code。如果我們在實作 API 時,回傳了 -EINPROGRESS,那一定要在後續的流程中呼叫 user program 的 callback function,不然有可能 user program 就會陷入一直等待 callback 的迴圈中。

舉例來說,我們在 update API function 中,當 request 被加入到 queue 後,即回傳 -EINPROGRESS 狀態給 user program:

 1static int cherie_crypto_engine_update(struct ahash_request *req)
 2{
 3	int ret;
 4	struct cherie_crypto_engine *engine = get_engine();
 5	struct cherie_request_state *state = ahash_request_ctx(req);
 6
 7	state->algo_op = ALGO_UPDATE;
 8	ret = cherie_crypto_request_enqueue(req);
 9	kthread_queue_work(ctx->kworker, &ctx->do_requests);
10	return ret; // ret is -EINPROGRESS if the request was added to queue.
11}

那麼就要確保在 worker 執行任務過程中會呼叫 request→complete callback function,好讓 user program 知道他可以繼續執行下一個 API call。

結論

本篇主要是說明在 Linux kernel 的 crypto subsystem 之下,運用 crypto queue 來處理多個 request 同時對一個 hardware crypto engine 的情境,並且搭配 worker 來實現 asynchronous request 流程。其中比較需要考量的是由於 worker 是唯一能與 crypto engine 互動的 thread,因此 worker 需要處理的任務和順序,是需要根據 Crypto API 流程和 crypto engine 本身的功能而設計的。

當然,如果不想這麼麻煩的話,在 Linux kernel v4.9.0 以上的版本,有提供抽象層 crypto/engine.h,它包含上述所提及的 queue 和 worker 的機制以及流程控制等,可以讓硬體供應商更方便的將 crypto engine 整合到 Linux kernel 中。