Before you start#
Review memory ownership and DMA, locking and sleeping, and credentials. This chapter reads upstream Linux v6.12 as a historical teaching baseline. Algorithms, hardware acceleration, key types and random readiness depend on configuration and the running kernel. No generated secret or key should be printed in the exercises.
Learning goals#
Trace a synchronous hash transform and a random-read readiness branch; distinguish request completion from submission; describe key lifetime and permission; and select cryptographic properties for a concrete threat model.
Source walkthrough#
- Allocate transform
Select a registered algorithm and handle failure
- Submit request
Retain descriptor, buffers and key for required lifetime
- Completion
Wait for synchronous or asynchronous final status
- Use output
Treat failed authentication and error outputs as invalid
The kernel Crypto API is an interface to registered algorithm implementations. A transform (often called a tfm) holds selected algorithm and configuration state for a consumer. A generic algorithm name can have multiple registered implementations, including software and optional hardware drivers, with priorities and selection constraints. Allocating sha256 does not establish which implementation was selected on a particular machine. The algorithm registry and Crypto API documentation describe the framework; a particular driver and its hardware determine actual acceleration and error behavior.
Cryptographic properties answer different questions. A hash maps arbitrary-length input to a fixed-length digest; equality with a trusted expected digest can detect a change, but does not by itself identify who supplied either value. A message authentication code (MAC) authenticates data to parties sharing a secret key; a bare hash is not a MAC. Encryption provides confidentiality under a specified construction but does not necessarily detect modification. An authenticated-encryption-with-associated-data (AEAD) construction provides confidentiality and integrity for ciphertext plus authentication of associated data under its key, nonce and algorithm requirements. A digital signature can authenticate data to a verifier with an appropriate public-key trust policy; it does not prove the signed code is safe or fresh. These are security goals, not guarantees against a compromised endpoint or incorrect key management. NIST hash, MAC, and signature standards define specific primitives and requirements; use the construction's own specification rather than inferring properties from its name.
Nonce requirements are algorithm-specific. For common AEAD schemes, reusing a nonce under the same key can destroy security; a counter needs persistence and coordination sufficient to prevent reuse across restarts and multiple writers. For example, RFC 5116 specifies the AEAD interface and discusses nonce uniqueness requirements, while individual algorithms may impose additional constraints. Do not invent a cipher mode, nonce generator, protocol, or error-handling rule from these examples. Use a reviewed construction and documented API, manage keys separately, and reject unauthenticated data rather than treating a decryption output buffer as trustworthy after authentication failure.
Synchronous hash dispatch
A synchronous hash (shash) operation uses a transform and a shash_desc containing per-operation state. A consumer allocates the transform for a selected algorithm, allocates and initializes a suitable descriptor, feeds data through update, finalizes into an appropriately sized digest buffer, handles errors and frees resources. The generic SHA-256 implementation is one registered implementation; architecture-specific or accelerated alternatives may be chosen instead.
In crypto/shash.c, this exact v6.12 GPL-2.0-or-later excerpt is crypto_shash_update, with surrounding declarations omitted:
return crypto_shash_alg(desc->tfm)->update(desc, data, len);
The expression obtains the hash algorithm associated with the descriptor's transform, invokes its update method with the descriptor, data pointer and byte length, and returns that implementation's status. It is dispatch, not a hard-coded SHA-256 calculation. The descriptor and input must remain valid for the call; the consumer must check the return before treating the operation as successful. crypto_shash_final dispatches the final callback. Failure in allocation, initialization, update or finalization requires cleanup and must not be represented as a valid digest. The transform's type and algorithm contract determine descriptor size, output size and whether a key is needed.
Requests, scatterlists and asynchronous completion#
An asynchronous hash (ahash), symmetric cipher (skcipher) or AEAD operation commonly uses a request object containing an algorithm-specific transform, input/output description and callback information. A scatterlist describes segments of memory without requiring one contiguous buffer. It is not a guarantee that any arbitrary pointer remains valid, or that every device can DMA every segment. Mapping, alignment, segment constraints and cache ownership depend on the implementation and DMA API. For AEAD, lengths and placement of associated data, plaintext, ciphertext and authentication tag follow the specific API contract.
Submission and completion can be different moments. An implementation may complete synchronously or return an in-progress or backlog status and call the completion callback later under the framework's rules. The request, callback context, scatterlists, buffers and transform must remain valid until the relevant completion. Freeing an input buffer immediately after asynchronous submission creates a potential use-after-free even if a fast software implementation happened to finish during a test. A callback's execution context can differ from the submitter's, so it must not assume sleeping is permitted. When a caller uses the kernel's crypto_wait_req helpers to wait for completion, that caller must be in a context where waiting is allowed and must distinguish the accepted in-progress result from final success or error.
The waiting helper is crypto_wait_req in include/linux/crypto.h, v6.12, SPDX GPL-2.0-or-later (exact, unabridged):
static inline int crypto_wait_req(int err, struct crypto_wait *wait)
{
switch (err) {
case -EINPROGRESS:
case -EBUSY:
wait_for_completion(&wait->completion);
reinit_completion(&wait->completion);
err = wait->err;
break;
}
return err;
}
The helper sleeps only for -EINPROGRESS and -EBUSY, and then returns the status stored by the callback. Because it calls wait_for_completion, it belongs only in a context that may sleep. The excerpt does not show the callback that stores wait->err.
The completion callback is crypto_req_done in crypto/api.c, v6.12, SPDX GPL-2.0-or-later (exact, export line omitted):
void crypto_req_done(void *data, int err)
{
struct crypto_wait *wait = data;
if (err == -EINPROGRESS)
return;
wait->err = err;
complete(&wait->completion);
}
The callback returns early on the in-progress notice, then stores the final error and completes the waiter. The excerpt does not show which drivers call it, or when a driver releases the request buffers.
- CallerCrypto API
submit request with buffers and transform
- Crypto APIImplementation
dispatch to the selected implementation
- ImplementationCaller
Return. status -EINPROGRESS or -EBUSY
- Implementationcrypto_req_done
operation finishes later
- crypto_req_doneCaller
Return. store wait->err and complete the waiter
Time runs downward. Dashed arrows show returns.
Read the text version
- Caller → Crypto API: submit request with buffers and transform
- Crypto API → Implementation: dispatch to the selected implementation
- Implementation → Caller (return): status -EINPROGRESS or -EBUSY
- Implementation → crypto_req_done: operation finishes later
- crypto_req_done → Caller (return): store wait->err and complete the waiter
Consider a synthetic AEAD decrypt: an application supplies ciphertext, associated data, a nonce and tag through the correct request layout. If the tag check fails, the final result reports authentication failure, commonly -EBADMSG for this API family. The caller must discard or otherwise treat the output as invalid; it must not consume partially written plaintext merely because the buffer contains bytes. If submission reports an asynchronous operation, the final callback or wait result—not the initial submission alone—determines success. The exact algorithm, tag length and buffer overlap rules still need to be read for the selected transform.
A key must be set on transforms that require one and retained according to the implementation's lifetime contract. A transform is not itself a secure protocol, and hardware offload does not guarantee key isolation or side-channel resistance. A failure to allocate an algorithm, unsupported parameters, invalid lengths, device reset or callback error must be handled separately. Record algorithm name and selected driver when investigating a performance or correctness difference rather than assuming generic names identify one implementation.
Kernel keys, keyrings and revocation#
The kernel key management subsystem holds objects with a type, description, owner, permissions, payload and lifetime state. Keyrings are key objects containing links to keys or other keyrings; a process can have thread, process and session keyring relationships. A key serial number is an identifier, not possession of the payload or unconditional permission. Permission checks include ownership and possession semantics as well as applicable security hooks. Search follows specific keyring and permission rules. Quotas, allocation failure, expiry and invalidation can make a requested key unavailable even when an identifier was valid earlier.
A key can be revoked while references still exist. The object may remain allocated until references and deferred reclamation permit destruction, while its state blocks permitted uses according to the key type and operation. In security/keys/key.c, key_revoke takes the key semaphore for writing, sets the revoked bit once, sends a notification, invokes a type-specific revoke callback if present, records a revocation time and schedules garbage collection. It then unlocks. Revocation, expiry, unlinking a key from one keyring and final memory reclamation are distinct events. RCU and references protect different parts of keyring lookup and lifetime; a caller must follow the relevant API rather than keeping an unreferenced pointer after lookup.
A trusted key type can use configured hardware or platform trust services where supported; an encrypted key type protects key material under a master key according to its implementation. These names are not proof that every machine has a TPM, that every secret never appears in memory, or that the host is uncompromised. Key type, policy, possession, access control, payload handling and device configuration must be checked for the actual system. Do not install keys, change keyring permissions or print key payloads in the observation lab.
Random input and CRNG readiness#
The random subsystem mixes entropy inputs into pools and uses a cryptographic random number generator (CRNG) for output. Input contribution, readiness estimation, reseeding and hardware random-number generator trust are distinct questions. A hardware generator can be useful input but trusting its reported quality is a configuration and threat-model decision; the presence of a device named hwrng alone does not establish independent unpredictability. Early boot deserves attention because sufficient initialization may not yet have occurred.
This exact excerpt from getrandom in drivers/char/random.c is v6.12, dual-licensed GPL-2.0 OR BSD-3-Clause; copyright Jason A. Donenfeld, Matt Mackall and contributors. Surrounding flag validation and buffer import are omitted:
if (!crng_ready() && !(flags & GRND_INSECURE)) {
if (flags & GRND_NONBLOCK)
return -EAGAIN;
ret = wait_for_random_bytes();
if (unlikely(ret))
return ret;
}
If the CRNG is not ready and insecure output was not requested, the nonblocking flag returns -EAGAIN instead of waiting. Otherwise the function waits for readiness and propagates a wait error. If the CRNG is ready, this block is skipped; later code imports the userspace buffer and copies random output, which can itself fail. GRND_INSECURE deliberately changes the early-readiness guarantee and is not a shortcut for generating secrets. The preceding code rejects unsupported flags and the incompatible GRND_INSECURE | GRND_RANDOM combination with -EINVAL.
For this v6.12 source, modern /dev/random and /dev/urandom behavior should not be taught using obsolete claims that one is indefinitely “stronger” because it exhausts an entropy counter while the other does not. Their interfaces and early-boot readiness behavior have historical nuances, and getrandom with default flags supplies a clear initialization-aware interface. Check the actual release's documentation and implementation, handle short reads and errors, and choose an API appropriate to the application's requirements. A return of random-looking bytes is not a proof of secrecy if the system was compromised or output was logged.
Worked source cases#
For a source-only hash example, take bytes abc. A successful SHA-256 operation is expected to yield the public test-vector digest shown in the lab. The shash dispatch excerpt establishes that the selected implementation receives an update call, not that your host chose the generic implementation or that the digest authenticates the source. If a caller receives an update error, final output must not be presented as a successful measurement. A known public digest is useful for checking a computation; an attacker who can replace both a file and an unauthenticated expected digest can make them agree.
For the random branch, consider a system before CRNG readiness. A request with GRND_NONBLOCK and without GRND_INSECURE returns -EAGAIN; a default request enters the wait path and can return an interrupt/error instead of output. Once ready, the code proceeds to user-buffer import and generation. This is a source-derived state table, not a claim that this host was observed during early boot. A second process reading the pool does not by itself imply that the first process's output is predictable or that a simple counter measures remaining cryptographic strength.
Selecting a transform and accounting for errors#
Suppose a kernel consumer requests a named hash transform and allocation fails because the requested algorithm is unavailable or memory cannot be obtained. The returned error is an allocation result, not an all-zero digest. After successful allocation, a descriptor must be sized for the selected transform and initialized before updates. If one update returns an error, the consumer must release its resources and report failure instead of finalizing and publishing a digest as though all input were processed. A stream split into two updates must preserve byte order: hashing a followed by bc describes the same byte sequence as hashing abc once, but hashing only the first segment is a different computation. The source dispatch excerpt identifies the method call; the selected implementation owns its internal state and algorithm-specific checks.
- Allocate transform
A failure returns an error, not a zero digest
- Set key if required
An invalid key length or restriction ends the sequence
- Initialize descriptor
Size the shash descriptor for the selected transform
- Update in order
Any update error releases resources and reports failure
- Finalize and free
Publish the digest only after all input has been processed
For a key-bearing transform, setting the key can fail because of an invalid key length or implementation restriction. An asynchronous request can then fail independently during submission or completion. A complete state table therefore includes allocation, key setup, request preparation, submission, completion and cleanup, with each acquired object released only after its last user. Do not assume an error left a destination buffer unchanged, or that a hardware reset left in-flight work completed. These are reasons to read a particular API and driver, not a claim that every crypto implementation has the same failure timing.
Practice#
Read-only and own-process observation lab
On an authorized Linux host or disposable guest, inspect public algorithm metadata and calculate a known public test vector. Neither command installs a kernel key or changes crypto policy:
uname -r
if [ -r /proc/crypto ]; then sed -n '1,36p' /proc/crypto; else echo '/proc/crypto unavailable'; fi
if command -v sha256sum >/dev/null 2>&1; then printf abc | sha256sum; else echo 'sha256sum unavailable'; fi
if [ -r /proc/keys ]; then echo '/proc/keys is readable; contents intentionally not printed'; else echo '/proc/keys unavailable or restricted'; fi
For the standard abc input, SHA-256 is ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad. Matching this public vector checks the tool's result for that input; it does not authenticate a downloaded file, prove which kernel implementation the userspace tool used, or establish hardware acceleration. /proc/crypto lists registered kernel algorithms where available; multiple entries and priorities need interpretation. sha256sum is userspace and can compute independently of the kernel Crypto API. /proc/keys visibility and permissions vary, and this lab does not print key names, serials or payloads.
The next command calls getrandom in its own Python process and prints only the count, never the returned bytes:
python3 - <<'PY'
import os
try:
result = os.getrandom(16, os.GRND_NONBLOCK)
print('getrandom returned', len(result), 'bytes; contents not displayed')
except BlockingIOError:
print('CRNG not ready for this nonblocking request')
except (AttributeError, OSError) as exc:
print('getrandom unavailable or restricted:', type(exc).__name__, getattr(exc, 'errno', None))
PY
A ready system normally reports 16 bytes, but a short result, EAGAIN, unavailable Python API, syscall restriction or other error must be recorded rather than hidden. Printing a length does not reveal or test the unpredictability of the bytes. Discard the data and do not use this diagnostic output as a key. The process's permissions or container policy can affect observation; do not disable security or install packages to force success.
Source-only alternative: report the abc expected digest as supplied evidence, then fill a readiness table for unready/nonblocking, unready/default and ready requests using the quoted source. Add a key-revocation timeline showing revoked state before final object reclamation. These are source and published-vector claims, not a runtime observation. Deliver either that table or the actual metadata and return-count result with kernel and tool versions.
Knowledge check#
- Why does a matching SHA-256 digest not authenticate a file if the expected digest is untrusted?
- What must remain alive after an asynchronous crypto request returns in progress?
- What should a caller do with an AEAD output buffer after tag verification fails?
- Does
key_revokenecessarily free every reference immediately? - What does
getrandomreturn for an unready CRNG withGRND_NONBLOCK, absentGRND_INSECURE?
Answers#
- An attacker able to replace both file and expected digest can supply a matching pair. Authentication needs a trusted MAC or signature verification context, not only equality.
- The request, transform, buffers, scatterlists and callback context must remain valid according to the API until completion. The eventual completion result determines success.
- Treat it as unauthenticated and do not consume it as valid plaintext. The buffer can contain partial output even though the operation failed.
- No. Revocation changes state under the key semaphore and schedules cleanup; existing references and reclamation rules still govern lifetime.
- The shown v6.12 branch returns
-EAGAIN. A default blocking request waits, and a wait or later buffer operation can still fail.
Checkpoint#
Submit a hash and random readiness table plus an asynchronous request lifetime timeline. Ready means a digest is distinguished from authentication, in-progress from completion, revoke from free, and early-boot EAGAIN from weak-output selection. Retry with the quoted source branches if any result is inferred from the wrong stage. This is readiness practice, not certification.