Security Linux Kernel Crypto Subsystem of Linux Kernel - Asynchronous & Synchronous

在 crypto subsystem 中,crypto API 分成 asynchronous (異步) 和 synchronous (同步) 兩種機制。

最早版本的 crypto API 其實只有 synchronous crypto API,但隨著要處理的資料量增加,運算和資料傳輸時間也可能大幅拉長,此時 synchronous crypto API 有可能讓處理流程陷入較長時間的等待,因此後來引入了 asynchronous crypto API,供使用者依據自己的使用場景來選擇適合的機制。

而 asynchronous 與 synchronous crypto API 在命名設計上有所區別,asynchronous 會在前綴多加一個 a 字,反之 synchronous 則是 s 字,以 hash 為例:

1// asynchronous API
2int crypto_ahash_digest(struct ahash_request *req);
3
4// synchronous API
5int crypto_shash_digest(struct shash_desc *desc, const u8 *data,
6                        unsigned int len, u8 *out);

除了命名之外,由於兩種機制的處理流程不同,因此所需的參數也會有所不同。

以下同樣以 hash crypto algorithm 為例子,說明 synchronous 和 asynchronous crypto API 的差異和使用情境。

Synchronous hash API

API document: https://docs.kernel.org/crypto/api-digest.html#synchronous-message-digest-api

API 呼叫流程:

Synchronous hash API 中有一個重要的參數 struct shash_desc *desc ,它是 state handler,用以保存運算流程中所需的狀態數值。例如,在 API 的呼叫流程中,crypto_shash_update() 可以被多次呼叫,讓使用者可以放入多組需要進行運算的 message,而當 crypto engine 運算完一組 message 後,可能有些中間狀態是需要被保存起來的,這些狀態數值就會放在 state handler 中。

1struct shash_desc {
2	struct crypto_shash *tfm;
3
4  // store required state for crypto engine
5	void *__ctx[] __aligned(ARCH_SLAB_MINALIGN); 
6};

因此當使用者在呼叫 API 前,會需要自己分配一塊足夠大小的記憶體,以讓 crypto engine 能夠存放這些狀態。在 transformation implementation 中會設定好 crypto engine 所需的狀態儲存空間大小,使用者只需要呼叫特定 API 即可取得。

1unsigned int size;
2struct crypto_shash *hash; // transformation object or called cipher handler
3struct shash_desc *desc; // state handler
4
5hash = crypto_alloc_shash(name, 0, 0); // create a transformation object
6
7// get a required desc size for crypto engine via `crypto_shash_descsize` API
8size = sizeof(struct shash_desc) + crypto_shash_descsize(hash);
9desc = kmalloc(size, GFP_KERNEL);

建立好 shash_desc 之後,接著執行初始化的 API,這個 API 主要會呼叫 transformation implementation 的 init function,用意是讓對應的 crypto engine 能夠進行初始化或是重置等,以準備接下來的運算行為。

1int rc;
2rc = crypto_shash_init(desc);
3// error handling

初始化完成後,就可以將呼叫 update API 來對指定的 message 進行 hash 運算。

1rc = crypto_shash_update(desc, message, message_len);
2// error handling

最後,呼叫 final 來取得 hash 結果。

1u8 result[DIGEST_SIZE];
2rc = crypto_shash_final(desc, result);
3// error handling

基本上 synchronous API 使用方式跟一般應用端的 crypto library 很相似,只要順序的呼叫對應流程的 API,並且針對返回的結果進行 error handling 即可。

Synchronous crypto API 用起來雖然直覺,但是卻不適用於一些場景,除了一開始有提到 synchronous 機制會造成 block 之外,另一個可能的問題是,當需要處理的資料為不連續記憶體區段時,synchronous crypto API 就不是這麼好用。可以看到之前所提及的例子,其中 crypto_shash_update 的輸入參數 message 為一段連續記憶體的 buffer,假設目前有好幾段資料,那就必須要呼叫 crypto_shash_update 多次,才能夠傳入所有的資料。

Asynchronous hash API

Asynchronous crypto API 提供了非同步的機制和引入 struct scatterlist ,來改善上述所提到的問題。

 1struct ahash_request {
 2	struct crypto_async_request base;
 3
 4	unsigned int nbytes;
 5	struct scatterlist *src;
 6	u8 *result;
 7
 8	/* This field may only be used by the ahash API code. */
 9	void *priv;
10
11	void *__ctx[] CRYPTO_MINALIGN_ATTR;
12};
13
14int crypto_ahash_digest(struct ahash_request *req);

從 API 中可以看到參數一率改成 struct ahash_request ,ahash_request 結構中包含重要的成員 struct scatterlist *,struct scatterlist 用來描述一段連續 physical memory 區段,而它可以是 chain 的形式,這也意味著能夠將多個 physical memory 區段串連成一個 list。

 1typedef void (*crypto_completion_t)(struct crypto_async_request *req, int err);
 2
 3struct crypto_async_request {
 4	struct list_head list;
 5	crypto_completion_t complete;
 6	void *data;
 7	struct crypto_tfm *tfm;
 8
 9	u32 flags;
10};

另外,struct crypto_async_request 則是包含一個 callback function crypto_completion_t, 當運算完成之後,則會透過此 callback 來通知使用者接續處理完成的流程。

由於是 asynchronous 非同步機制,因此 crypto engine 在處理 request 時,行為和流程也和 synchronous 同步機制有蠻大的差異,其中常見的實作方式加入 request queue 來管理多個 request,當使用者呼叫 update API 發送 request 時,則會將 request 加入到 queue 中,並直接回傳處理中 (-EINPROGRESS) 的狀態訊息。

以下為簡單的 asynchronous hash API 使用例子:

 1const u32 result_len = 16;
 2struct crypto_ahash *tfm;
 3struct ahash_request *req;
 4u8 *result;
 5
 6result = kmalloc(result_len, GFP_NOFS);
 7
 8tfm = crypto_alloc_ahash(0, 0, CRYPTO_ALG_ASYNC);
 9req = ahash_request_alloc(tfm, GFP_NOFS);
10// set callback function
11ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP, callback_fn, NULL);
12// set input data
13ahash_request_set_crypt(req, sc, NULL, 32);
14
15err = crypto_ahash_init(req);
16
17err = crypto_ahash_update(req);
18if (err == -EINPROGRESS)
19{
20	//
21}
22
23err = crypto_ahash_final(req);
24if (err == -EINPROGRESS)
25{
26	//
27}

其他注意事項:

  1. 雖然命為 asynchronous hash API ,但實際上對應到的 crypto engine 實作方式不一定就都會以非同步的方式來處理,具體流程還是要依照各家廠商的實作內容為主。
  2. 如果使用者使用 asynchronous hash API,但是實際上對應的 transformation implementation 卻是 synchronous 型態,crypto subsystem 會主動進行相關的資料轉換,因此也是可以正常運作的。

結論

簡單介紹 asynchronous 和 synchronous crypto API 以及與 crypto engine 的溝通流程,表面上看起來 asynchronous 機制更有彈性,不過對於廠商來說,實際上要實作哪種機制可能會受到硬體或是其他實作層面的限制,因此還是要多方參考後才能知道哪種方式比較好。

References

  1. Linux Kernel Crypto API
  2. An overview of the crypto subsystem - The Linux Foundation