web-dev-qa-db-ja.com

GETRANDOM syscallとカーネルエントロピープールの状態との関係

GETRANDOMがビットマスク00で呼び出されると、エントロピーソースは/ dev/urandomからのものであり、内部エントロピープールに少なくとも128ビットのエントロピーがあるまで、CSPRNG出力はブロックされます。プールの状態は/proc/sys/kernel/random/entropy_availで読み取り可能ですか?それ以外の場合、どこで確認できますか?

ここでの目標は、エントロピープールの内部状態にカスタムのしきい値(256ビット)を設定してから、それを使用してキーなどを生成できるようにすることです。

3
maqp

Sysctlとブロッキングプール。

_kernel.random.read_wakeup_threshold_ sysctlの値を増やすことができます。このsysctlは ブロッキングプール の動作を変更し、エントロピーの推定値がこの値を超えるまでブロッキングプールのユーザーを待機させます。 random(4) のマンページから:

_read_wakeup_threshold
    This file contains the number of bits of entropy required for
    waking up processes that sleep waiting for entropy from
    /dev/random.  The default is 64.
_

ただし、ここで説明するブロッキング動作は、非常に早いブート時のシステムにのみ適用されることに注意してください。十分なエントロピーが得られると、現在の推定値がどれだけ低くても、非ブロッキング動作に変わります。これは、エントロピーのビット数が1回必要なだけで、暗号化された安全な疑似ランダムデータを事実上無制限に作成できるためです。エントロピーの見積もりが下がっても、この事実は変わりません。

正確な質問に答える

さて、非ブロッキングプールのこの初期しきい値を変更することは可能ですか(要点をすべて学習したくない場合は、この回答の最後までスキップしてください)?そうではなかったのではないかと疑ったが、確信が持てなかったので、入手できる最も信頼できるドキュメントであるソースを調べた。 syscall getrandom(2) は、カーネルランダム性ドライバーでは defined です。 これはLinuxカーネル4.14に固有であることに注意してください(ランダム性ドライバーへの主な変更は4.8で行われました)。

説明のために私が追加したコメント。

getrandom()

_SYSCALL_DEFINE3(getrandom, char __user *, buf, size_t, count,
        unsigned int, flags)
{
    int ret;

    // If the flags bitmask is invalid, return an error.
    if (flags & ~(GRND_NONBLOCK|GRND_RANDOM))
        return -EINVAL;

    // Cap the requested size at the maximum value.
    if (count > INT_MAX)
        count = INT_MAX;

    // If the blocking pool is selected, read from it and return.
    // The _random_read() function will deal with blocking.
    if (flags & GRND_RANDOM)
        return _random_read(flags & GRND_NONBLOCK, buf, count);

    // At this point, we know the non-blocking pool was selected.

    // Is the CRNG ready? Evaluates true if it is not.
    if (!crng_ready()) {
        // If we want to bail out and not block, return -EAGAIN.
        if (flags & GRND_NONBLOCK)
            return -EAGAIN;

        // Otherwise, block until random bytes become available.
        ret = wait_for_random_bytes();
        if (unlikely(ret))
            return ret;
    }

    // Finally, read from the non-blocking pool and return.
    return urandom_read(NULL, buf, count, NULL);
}
_

OK、これのほとんどは自明ですが、関数crng_ready()およびwait_for_random_bytes()は何のためのものですか?後者は defined が同じファイルにあります。

wait_for_random_bytes()

_int wait_for_random_bytes(void)
{
    // If crng_ready() returns true (which is likely), return 0.
    if (likely(crng_ready()))
        return 0;

    // Otherwise, wait until it does return true before returning.
    return wait_event_interruptible(crng_init_wait, crng_ready());
}
EXPORT_SYMBOL(wait_for_random_bytes);
_

これで、getrandom()の定義で、ノンブロッキングプールが選択されている場合、crng_ready()がtrueを返すかどうかを確認することがわかりました。 trueが返されない場合は、返されるまでスリープします。 crng_ready()は何をしますか?単純なマクロとして defined であることがわかります。

crng_ready()

_// If the crng_init variable is > 0 (which is likely), evaluate true.
#define crng_ready() (likely(crng_init > 0))
_

変数はゼロから始まりますが、重要なのは、正確に1に設定されている場所です。これは、crng_fast_load()関数 ここで定義 で行われているようです。

crng_fast_load()

_static int crng_fast_load(const char *cp, size_t len)
{
    unsigned long flags;
    char *p;

    // Enter the atomic section.
    if (!spin_trylock_irqsave(&primary_crng.lock, flags))
        return 0;

    // If crng_ready() is already true, leave the atomic section and return.
    if (crng_ready()) {
        spin_unlock_irqrestore(&primary_crng.lock, flags);
        return 0;
    }

    // Mix in the values at cp with the CRNG state. Increment crng_init_cnt
    // for each byte from cp that gets mixed in (up to len times).
    p = (unsigned char *) &primary_crng.state[4];
    while (len > 0 && crng_init_cnt < CRNG_INIT_CNT_THRESH) {
        p[crng_init_cnt % CHACHA20_KEY_SIZE] ^= *cp;
        cp++; crng_init_cnt++; len--;
    }

    // Leave the atomic section.
    spin_unlock_irqrestore(&primary_crng.lock, flags);

    // If crng_init_cnt is >= CRNG_INIT_CNT_THRESH, set crng_init to 1.
    if (crng_init_cnt >= CRNG_INIT_CNT_THRESH) {
        invalidate_batched_entropy();
        crng_init = 1;
        wake_up_interruptible(&crng_init_wait);
        pr_notice("random: fast init done\n");
    }
    return 1;
}
_

このことから、crng_fast_load()が受け取るバイトごとに_crng_init_cnt_がインクリメントされることがわかります。関数は、さまざまなエントロピー収集関数でブート時に早期に呼び出され、可能な限り多くのデータをプールに早期に追加しますオン。もうすぐ着きます!最後に行うことは、_CRNG_INIT_CNT_THRESH_、 ここで定義 の値を明確にすることです。

CRNG_INIT_CNT_THRESHおよびCHACHA20_KEY_SIZE

_#define CRNG_INIT_CNT_THRESH (2*CHACHA20_KEY_SIZE)
_

したがって、これは二重_CHACHA20_KEY_SIZE_です。これは、ヘッダーファイル_crypto/chacha20.h_の defined です。

_#define CHACHA20_KEY_SIZE   32
_

つまり_CRNG_INIT_CNT_THRESH_は64です。そしてあなたの答えがあります!

要約

これがどこにつながるのか見てみましょう:

  • _CHACHA20_KEY_SIZE_は32にハードコードされています。
  • _CRNG_INIT_CNT_THRESH_はdouble _CHACHA20_KEY_SIZE_で64になります。
  • _crng_init_cnt_は、収集された初期のランダム性のバイトごとに増分されます。
  • 64バイト以上のランダムさが収集されると、_crng_init_は1に設定されます。
  • _crng_init_が1の場合、crng_ready()はtrueと評価されます。
  • crng_ready()がtrueと評価されると、getrandom()が再開して戻ります。

getrandom()が再開して戻る前に必要な初期エントロピーの量は、実際には128ビットではありません。すでに64バイト(512ビット)にハードコードされており、必要な量の2倍です

3
forest