From 7b96719c5dc378970d5df4b6d95cce68b20ceae9 Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Thu, 12 Nov 2026 08:42:37 +0700 Subject: [PATCH 1/2] net: AF_ALG sockets with the type hash, as af_alg.c and algif_hash.c --- CHANGELOG.md | 1 + Cargo.lock | 1 + core/rk-syscall/src/net.rs | 1 + net/rk-net/Cargo.toml | 1 + net/rk-net/src/alg/hash.rs | 202 +++++++++++++++++++++++ net/rk-net/src/alg/mod.rs | 316 ++++++++++++++++++++++++++++++++++++ net/rk-net/src/alg/tests.rs | 153 +++++++++++++++++ net/rk-net/src/lib.rs | 1 + sec/rk-crypto/src/modes.rs | 4 +- 9 files changed, 678 insertions(+), 2 deletions(-) create mode 100644 net/rk-net/src/alg/hash.rs create mode 100644 net/rk-net/src/alg/mod.rs create mode 100644 net/rk-net/src/alg/tests.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c8c30..b65a6b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This file records each release. The format follows [Keep a Changelog](https://ke ### Added +- The `AF_ALG` sockets with the type `hash`, as crypto/af_alg.c and crypto/algif_hash.c of Linux. A socket binds to a hash of `rk-crypto` by its name, `setsockopt(SOL_ALG, ALG_SET_KEY)` sets the key of an HMAC, and `accept` gives a socket for one computation. A `send` with `MSG_MORE` adds data, and a `recv` gives the digest. An `accept` on the socket of a computation copies its state. As with `crypto.af_alg_restrict=1` of Linux, a name that is not in the allow list gives `ENOENT`, and a name that needs a capability gives `ENOENT` without `CAP_SYS_ADMIN` or `CAP_NET_ADMIN` (#541). - The crypto API in the new crate `rk-crypto`, as crypto/ of Linux: the hashes SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512, the template `hmac`, the block cipher AES, and the templates `ecb`, `cbc`, `ctr` and `xts` with ciphertext stealing. The code is plain Rust. The unit tests use the vectors of crypto/testmgr.h of Linux (#541). - The event log of the firmware at `/sys/kernel/security/tpm0/binary_bios_measurements`, as drivers/char/tpm/eventlog of Linux. The kernel copies the log area of the ACPI `TPM2` table on x86-64, or the area of `linux,sml-base` and `linux,sml-size` of the device tree node of the TPM on arm64. The file has the first event and each next event of the crypto agile format, up to the first event that is not valid, as `tpm2_binary_bios_measurements_show` of Linux. On x86-64 the `tpm` boot test replays the log of SeaBIOS with `tpm2_eventlog`, and the SHA-256 PCRs of the log must be the PCRs of `tpm2_pcrread` (#541). - The flowtables of nf_tables, as nf_tables_api.c and nft_flow_offload.c of Linux: `NFT_MSG_NEWFLOWTABLE`, `NFT_MSG_GETFLOWTABLE`, `NFT_MSG_DELFLOWTABLE` and `NFT_MSG_DESTROYFLOWTABLE`, with the ingress hook, the priority, the flags and the list of devices, and the `flow_offload` expression in the forward hook. A device can be in one flowtable of a table only (`EEXIST`), a flowtable that a rule uses cannot be deleted (`EBUSY`), and a device that goes away leaves the flowtable. The packets do not take a fast path: `flow_offload` does not change them (#542). diff --git a/Cargo.lock b/Cargo.lock index 182be57..4d4322a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,7 @@ name = "rk-net" version = "0.5.22" dependencies = [ "rk-abi", + "rk-crypto", "rk-frame", "rk-vfs", ] diff --git a/core/rk-syscall/src/net.rs b/core/rk-syscall/src/net.rs index f453048..c3685ee 100644 --- a/core/rk-syscall/src/net.rs +++ b/core/rk-syscall/src/net.rs @@ -240,6 +240,7 @@ fn family_socket( let protocol = u32::try_from(protocol).map_err(|_| Errno::EINVAL)?; Ok(rk_net::packet::PacketSocket::new(kind, protocol)?) } + rk_net::alg::AF_ALG => Ok(rk_net::alg::AlgSocket::new(kind, protocol)?), AF_NETLINK => { let protocol = u32::try_from(protocol as i32).map_err(|_| Errno::EPROTONOSUPPORT)?; Ok(rk_net::netlink::NetlinkSocket::new(kind, protocol)?) diff --git a/net/rk-net/Cargo.toml b/net/rk-net/Cargo.toml index 21689ce..b50286d 100644 --- a/net/rk-net/Cargo.toml +++ b/net/rk-net/Cargo.toml @@ -11,6 +11,7 @@ publish.workspace = true [dependencies] rk-abi.workspace = true +rk-crypto.workspace = true rk-frame.workspace = true rk-vfs.workspace = true diff --git a/net/rk-net/src/alg/hash.rs b/net/rk-net/src/alg/hash.rs new file mode 100644 index 0000000..9d44f48 --- /dev/null +++ b/net/rk-net/src/alg/hash.rs @@ -0,0 +1,202 @@ +//! The operation sockets of the type `hash`, as crypto/algif_hash.c of Linux. +//! +//! A send adds its data to the digest. A send with `MSG_MORE` keeps the digest open for more +//! data. A send without it ends the digest, and the next send starts a new one. A receive gives +//! the digest and ends it if it is open. A receive with no data before gives the digest of no +//! data. A receive shorter than the digest gives its start, with `MSG_TRUNC`. `accept` on an +//! operation socket gives a new one with a copy of the open digest. + +use super::{AF_ALG, AllowList, Bound, Child, Tfm, get_socket_option}; +use crate::consts::{MSG_MORE, MSG_TRUNC, SOCK_SEQPACKET}; +use crate::options::Options; +use crate::{Message, PathOps, Received, Socket, next_ino}; +use alloc::boxed::Box; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; +use core::any::Any; +use rk_abi::Errno; +use rk_abi::native::socket::SOL_SOCKET; +use rk_crypto::Hash; +use rk_frame::sync::SpinLock; +use rk_vfs::inode::Result; + +/// The hashes that a program can bind, as `hash_allowlist` of Linux. +const ALLOW: AllowList = &[ + ("cmac(aes)", false), + ("hmac(md5)", false), + ("hmac(sha1)", false), + ("hmac(sha224)", false), + ("hmac(sha256)", false), + ("hmac(sha384)", false), + ("hmac(sha512)", true), + ("md4", false), + ("md5", false), + ("sha1", true), + ("sha224", false), + ("sha256", false), + ("sha384", false), + ("sha512", false), +]; + +/// Finds a hash, as `hash_bind` of Linux. +pub(super) fn bind(name: &str) -> Result> { + super::check_restriction(name, ALLOW)?; + rk_crypto::alloc_hash(name) +} + +/// The state of an operation socket, as `struct hash_ctx` of Linux. +struct Ctx { + hash: Box, + /// The digest is open: the last send had `MSG_MORE`. + more: bool, + /// The digest that the last send ended. + result: Option>, + /// The socket has no key yet. + nokey: bool, +} + +/// An operation socket of the type `hash`. +pub(super) struct HashSocket { + ino: u64, + bound: Arc, + ctx: SpinLock, + options: SpinLock, +} + +impl HashSocket { + pub(super) fn new(bound: Arc, hash: Box, nokey: bool) -> Arc { + Arc::new(Self { + ino: next_ino(), + bound, + ctx: SpinLock::new(Ctx { hash, more: false, result: None, nokey }), + options: SpinLock::new(Options::default()), + }) + } + + /// A socket with no key takes the key of the bound socket, as `hash_check_key` of Linux. + fn check_key(&self, ctx: &mut Ctx) -> Result<()> { + if !ctx.nokey { + return Ok(()); + } + self.bound.check_key(|tfm| { + let Tfm::Hash(h) = tfm; + ctx.hash = h.box_clone(); + })?; + ctx.nokey = false; + Ok(()) + } +} + +impl Drop for HashSocket { + fn drop(&mut self) { + let nokey = self.ctx.lock().nokey; + self.bound.release(nokey); + } +} + +/// Ends the digest of `ctx` and gives it. +fn finish(ctx: &mut Ctx) -> Vec { + let mut out = vec![0u8; ctx.hash.digest_size()]; + ctx.hash.finish(&mut out); + out +} + +impl Socket for HashSocket { + fn family(&self) -> u32 { + AF_ALG + } + + fn kind(&self) -> u32 { + SOCK_SEQPACKET + } + + fn ino(&self) -> u64 { + self.ino + } + + /// Gives a new operation socket with a copy of the open digest, as `hash_accept` of Linux. + fn accept(&self, _nonblock: bool) -> Result> { + let state = { + let mut ctx = self.ctx.lock(); + self.check_key(&mut ctx)?; + ctx.more.then(|| ctx.hash.box_clone()) + }; + let Child::Hash(child) = self.bound.accept()?; + if let Some(hash) = state { + let mut c = child.ctx.lock(); + c.hash = hash; + c.more = true; + } + Ok(child) + } + + /// Adds the data to the digest, as `hash_sendmsg` of Linux. + fn send(&self, _: &dyn PathOps, msg: Message, flags: u32, _: bool) -> Result { + let mut ctx = self.ctx.lock(); + self.check_key(&mut ctx)?; + let more = flags & MSG_MORE != 0; + let data = &msg.data; + if !ctx.more { + // A send that does not go on from an open digest drops the last digest. + ctx.result = None; + if data.is_empty() { + return Ok(0); + } + ctx.hash.init(); + } + ctx.hash.update(data); + if !more { + let out = finish(&mut ctx); + ctx.result = Some(out); + } + ctx.more = more; + Ok(data.len()) + } + + /// Gives the digest, as `hash_recvmsg` of Linux. + fn recv(&self, len: usize, _flags: u32, _nonblock: bool) -> Result { + let mut ctx = self.ctx.lock(); + self.check_key(&mut ctx)?; + let size = ctx.hash.digest_size(); + let n = len.min(size); + let result = match ctx.result.take() { + Some(r) if !ctx.more => r, + r => { + if r.is_none() && !ctx.more { + ctx.hash.init(); + } + ctx.more = false; + finish(&mut ctx) + } + }; + let flags = if len < size { MSG_TRUNC } else { 0 }; + Ok(Received { data: result[..n].to_vec(), len: n, flags, ..Received::default() }) + } + + fn name(&self) -> Vec { + (AF_ALG as u16).to_ne_bytes().to_vec() + } + + fn get_option(&self, level: u32, name: u32) -> Result> { + if level != SOL_SOCKET { + return Err(Errno::EOPNOTSUPP); + } + get_socket_option(&self.options, name) + } + + fn set_option(&self, level: u32, name: u32, value: &[u8]) -> Result<()> { + if level != SOL_SOCKET { + return Err(Errno::EOPNOTSUPP); + } + self.options.lock().set(name, value) + } + + fn poll(&self) -> u32 { + 0 + } + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/net/rk-net/src/alg/mod.rs b/net/rk-net/src/alg/mod.rs new file mode 100644 index 0000000..216dd84 --- /dev/null +++ b/net/rk-net/src/alg/mod.rs @@ -0,0 +1,316 @@ +//! The sockets of the family `AF_ALG`, as crypto/af_alg.c of Linux. +//! +//! A program makes a socket of the type `SOCK_SEQPACKET` and binds it to an algorithm with a type +//! and a name, for example `hash` and `sha256`. The bound socket holds the algorithm and its key. +//! `setsockopt` with the level `SOL_ALG` and `ALG_SET_KEY` sets the key. `accept` on the bound +//! socket gives an operation socket. The program writes the data to the operation socket and +//! reads the result from it. The module `hash` has the operation sockets of the type `hash`, as +//! crypto/algif_hash.c of Linux. +//! +//! The algorithms come from the crate `rk_crypto`. A program can bind only the algorithms of the +//! allow list of the type, as `af_alg_check_restriction` of Linux with `crypto.af_alg_restrict` +//! at its default value 1. A process with no `CAP_NET_ADMIN` and no `CAP_SYS_ADMIN` can bind +//! only the entries that are for all users. Another name gives `ENOENT`. +//! +//! An operation socket of an algorithm that needs a key and has none is a socket with no key, as +//! the `ops_nokey` of Linux. Its operations give `ENOKEY` until the bound socket has a key. The +//! key of the bound socket cannot change while it has an operation socket with a key. + +mod hash; +#[cfg(test)] +mod tests; + +use crate::consts::SOCK_SEQPACKET; +use crate::options::Options; +use crate::{Message, PathOps, Received, Socket, next_ino}; +use alloc::boxed::Box; +use alloc::sync::Arc; +use alloc::vec::Vec; +use core::any::Any; +use rk_abi::Errno; +use rk_abi::native::socket::{ + SO_ACCEPTCONN, SO_DOMAIN, SO_ERROR, SO_PROTOCOL, SO_TYPE, SOL_SOCKET, +}; +use rk_frame::sync::SpinLock; +use rk_vfs::cred::cap; +use rk_vfs::inode::Result; + +/// The address family of the sockets of the crypto API. +pub const AF_ALG: u32 = 38; +/// The level of the socket options of `AF_ALG`. +pub const SOL_ALG: u32 = 279; + +/// The options of the level `SOL_ALG` and the types of the control messages of `AF_ALG`, from +/// include/uapi/linux/if_alg.h of Linux. The option that sets the key. +pub const ALG_SET_KEY: u32 = 1; +/// The control message that sets the IV. +pub const ALG_SET_IV: u32 = 2; +/// The control message that sets the operation, encrypt or decrypt. +pub const ALG_SET_OP: u32 = 3; +/// The control message that sets the length of the associated data of an AEAD. +pub const ALG_SET_AEAD_ASSOCLEN: u32 = 4; +/// The option that sets the size of the tag of an AEAD. +pub const ALG_SET_AEAD_AUTHSIZE: u32 = 5; +/// The option that sets the entropy of a DRBG for a test. +pub const ALG_SET_DRBG_ENTROPY: u32 = 6; +/// The option that sets the key from a key of the kernel key ring. +pub const ALG_SET_KEY_BY_KEY_SERIAL: u32 = 7; + +/// The offset of `salg_name` in `struct sockaddr_alg`. +const NAME_OFFSET: usize = 24; +/// The size of `salg_type`. +const TYPE_LEN: usize = 14; +/// The one flag that `salg_feat` and `salg_mask` can have. +const CRYPTO_ALG_KERN_DRIVER_ONLY: u32 = 0x1000; + +/// An allow list: the name of an algorithm, and true if all users can bind it, as +/// `AF_ALG_UNPRIVILEGED`. +type AllowList = &'static [(&'static str, bool)]; + +/// True if the process can bind all the algorithms of an allow list, as `af_alg_capable`. +fn capable() -> bool { + let cred = rk_vfs::cred::current(); + cred.ns_capable(rk_vfs::user_ns::init(), cap::NET_ADMIN) || cred.capable(cap::SYS_ADMIN) +} + +/// Gives `ENOENT` if the process cannot bind `name`, as `af_alg_check_restriction` of Linux. +fn check_restriction(name: &str, list: AllowList) -> Result<()> { + match list.iter().find(|(n, _)| *n == name) { + Some(&(_, all)) if all || capable() => Ok(()), + _ => Err(Errno::ENOENT), + } +} + +/// The algorithm of a bound socket, as the `private` of a `struct alg_sock` of Linux. +enum Tfm { + Hash(Box), +} + +impl Tfm { + /// Finds the algorithm `name` of the type `kind`, as the `bind` of a `struct af_alg_type`. + fn bind(kind: &str, name: &str) -> Result { + match kind { + "hash" => hash::bind(name).map(Self::Hash), + _ => Err(Errno::ENOENT), + } + } + + fn needs_key(&self) -> bool { + match self { + Self::Hash(h) => h.needs_key(), + } + } + + fn set_key(&mut self, key: &[u8]) -> Result<()> { + match self { + Self::Hash(h) => h.set_key(key), + } + } +} + +/// The state of a bound socket that its operation sockets share. +struct Shared { + tfm: Option, + /// The count of operation sockets, as `refcnt`. + children: usize, + /// The count of operation sockets with no key, as `nokey_refcnt`. + nokey: usize, +} + +/// The shared state of a bound socket. +struct Bound(SpinLock); + +/// A new operation socket. +enum Child { + Hash(Arc), +} + +impl Child { + fn socket(self) -> Arc { + match self { + Self::Hash(s) => s, + } + } +} + +impl Bound { + /// Makes an operation socket, as `af_alg_accept` of Linux. An algorithm that needs a key + /// and has none gives a socket with no key. + fn accept(self: &Arc) -> Result { + let mut s = self.0.lock(); + let Some(tfm) = &s.tfm else { + return Err(Errno::EINVAL); + }; + let nokey = tfm.needs_key(); + let child = match tfm { + Tfm::Hash(h) => Child::Hash(hash::HashSocket::new(self.clone(), h.box_clone(), nokey)), + }; + s.children += 1; + if nokey { + s.nokey += 1; + } + Ok(child) + } + + /// An operation socket goes, as `af_alg_release_parent` of Linux. + fn release(&self, nokey: bool) { + let mut s = self.0.lock(); + s.children -= 1; + if nokey { + s.nokey -= 1; + } + } + + /// An operation socket with no key checks the key of the bound socket, as `hash_check_key` + /// of Linux. If the bound socket has a key now, `take` gets the algorithm, and the socket + /// has a key from now on. + /// + /// # Errors + /// + /// Gives `ENOKEY` if the bound socket has no key. + fn check_key(&self, take: impl FnOnce(&Tfm)) -> Result<()> { + let mut s = self.0.lock(); + match &s.tfm { + Some(tfm) if !tfm.needs_key() => take(tfm), + _ => return Err(Errno::ENOKEY), + } + s.nokey -= 1; + Ok(()) + } +} + +/// Reads an option of the level `SOL_SOCKET` of a socket of the family. +fn get_socket_option(options: &SpinLock, name: u32) -> Result> { + let int = |v: u32| (v as i32).to_ne_bytes().to_vec(); + Ok(match name { + SO_TYPE => int(SOCK_SEQPACKET), + SO_DOMAIN => int(AF_ALG), + SO_PROTOCOL | SO_ERROR | SO_ACCEPTCONN => int(0), + _ => options.lock().get(name)?, + }) +} + +/// The C string at the start of `bytes`, up to its first zero byte. +fn c_str(bytes: &[u8]) -> &[u8] { + bytes.split(|&b| b == 0).next().unwrap_or(bytes) +} + +/// A socket of the family `AF_ALG` that is not an operation socket, as `alg_proto_ops` of Linux. +pub struct AlgSocket { + ino: u64, + bound: Arc, + options: SpinLock, +} + +impl AlgSocket { + /// Makes a socket, as `alg_create` of Linux. + /// + /// # Errors + /// + /// Gives `ESOCKTNOSUPPORT` for a type other than `SOCK_SEQPACKET`, and `EPROTONOSUPPORT` for + /// a protocol other than 0. + pub fn new(kind: u32, protocol: u64) -> Result> { + if kind != SOCK_SEQPACKET { + return Err(Errno::ESOCKTNOSUPPORT); + } + if protocol as i32 != 0 { + return Err(Errno::EPROTONOSUPPORT); + } + Ok(Arc::new(Self { + ino: next_ino(), + bound: Arc::new(Bound(SpinLock::new(Shared { tfm: None, children: 0, nokey: 0 }))), + options: SpinLock::new(Options::default()), + })) + } +} + +impl Socket for AlgSocket { + fn family(&self) -> u32 { + AF_ALG + } + + fn kind(&self) -> u32 { + SOCK_SEQPACKET + } + + fn ino(&self) -> u64 { + self.ino + } + + /// Binds the socket to an algorithm, as `alg_bind` of Linux. The address is a + /// `struct sockaddr_alg` with at least one byte of `salg_name`. + fn bind(&self, _paths: &dyn PathOps, addr: &[u8]) -> Result<()> { + if addr.len() < NAME_OFFSET + 1 { + return Err(Errno::EINVAL); + } + let word = |at: usize| u32::from_ne_bytes(addr[at..at + 4].try_into().unwrap_or_default()); + let (feat, mask) = (word(16), word(20)); + if (feat | mask) & !CRYPTO_ALG_KERN_DRIVER_ONLY != 0 { + return Err(Errno::EINVAL); + } + // The last byte of each field is a zero byte, as Linux writes it. + let kind = c_str(&addr[2..2 + TYPE_LEN - 1]); + let name = c_str(&addr[NAME_OFFSET..addr.len() - 1]); + let kind = core::str::from_utf8(kind).map_err(|_| Errno::ENOENT)?; + let name = core::str::from_utf8(name).map_err(|_| Errno::ENOENT)?; + let tfm = Tfm::bind(kind, name)?; + let mut s = self.bound.0.lock(); + if s.children != 0 { + return Err(Errno::EBUSY); + } + s.tfm = Some(tfm); + Ok(()) + } + + fn accept(&self, _nonblock: bool) -> Result> { + Ok(self.bound.accept()?.socket()) + } + + fn send(&self, _: &dyn PathOps, _: Message, _: u32, _: bool) -> Result { + Err(Errno::EOPNOTSUPP) + } + + fn recv(&self, _len: usize, _flags: u32, _nonblock: bool) -> Result { + Err(Errno::EOPNOTSUPP) + } + + fn name(&self) -> Vec { + (AF_ALG as u16).to_ne_bytes().to_vec() + } + + fn get_option(&self, level: u32, name: u32) -> Result> { + if level != SOL_SOCKET { + return Err(Errno::EOPNOTSUPP); + } + get_socket_option(&self.options, name) + } + + /// Sets an option, as `alg_setsockopt` of Linux. The key cannot change while the socket has + /// an operation socket with a key. + fn set_option(&self, level: u32, name: u32, value: &[u8]) -> Result<()> { + if level == SOL_SOCKET { + return self.options.lock().set(name, value); + } + let mut s = self.bound.0.lock(); + if s.children != s.nokey { + return Err(Errno::EBUSY); + } + let Some(tfm) = s.tfm.as_mut() else { + return Err(Errno::ENOPROTOOPT); + }; + match (level, name) { + (SOL_ALG, ALG_SET_KEY) => tfm.set_key(value), + // The kernel has no keys of the key rings for `ALG_SET_KEY_BY_KEY_SERIAL`, as Linux + // with no `CONFIG_KEYS`. The types here have no size of a tag and no entropy. + _ => Err(Errno::ENOPROTOOPT), + } + } + + fn poll(&self) -> u32 { + 0 + } + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/net/rk-net/src/alg/tests.rs b/net/rk-net/src/alg/tests.rs new file mode 100644 index 0000000..5b6794e --- /dev/null +++ b/net/rk-net/src/alg/tests.rs @@ -0,0 +1,153 @@ +//! The tests of the sockets of the family `AF_ALG`. + +use super::*; +use crate::NoPaths; +use crate::consts::{MSG_MORE, MSG_TRUNC, SOCK_STREAM}; +use alloc::vec; + +const ABC: &str = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; +const EMPTY: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; +/// RFC 4231 test case 2: the key "Jefe". +const JEFE: &str = "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"; + +fn hex(b: &[u8]) -> alloc::string::String { + b.iter().map(|x| alloc::format!("{x:02x}")).collect() +} + +/// A `struct sockaddr_alg` for the type `kind` and the algorithm `name`. +fn addr(kind: &str, name: &str) -> Vec { + let mut a = vec![0u8; NAME_OFFSET + 64]; + a[..2].copy_from_slice(&(AF_ALG as u16).to_ne_bytes()); + a[2..2 + kind.len()].copy_from_slice(kind.as_bytes()); + a[NAME_OFFSET..NAME_OFFSET + name.len()].copy_from_slice(name.as_bytes()); + a +} + +fn bound(kind: &str, name: &str) -> Arc { + let s = AlgSocket::new(SOCK_SEQPACKET, 0).unwrap(); + s.bind(&NoPaths, &addr(kind, name)).unwrap(); + s +} + +fn send(s: &dyn Socket, data: &[u8], flags: u32) -> Result { + let msg = Message { data: data.to_vec(), ..Message::default() }; + s.send(&NoPaths, msg, flags, false) +} + +fn digest(s: &dyn Socket) -> alloc::string::String { + hex(&s.recv(64, 0, false).unwrap().data) +} + +#[test] +fn create_and_bind() { + assert_eq!(AlgSocket::new(SOCK_STREAM, 0).err(), Some(Errno::ESOCKTNOSUPPORT)); + assert_eq!(AlgSocket::new(SOCK_SEQPACKET, 1).err(), Some(Errno::EPROTONOSUPPORT)); + let s = AlgSocket::new(SOCK_SEQPACKET, 0).unwrap(); + assert_eq!(s.get_option(SOL_SOCKET, SO_TYPE).unwrap(), 5i32.to_ne_bytes()); + assert_eq!(s.get_option(SOL_SOCKET, SO_DOMAIN).unwrap(), 38i32.to_ne_bytes()); + assert_eq!(s.get_option(SOL_ALG, ALG_SET_KEY).err(), Some(Errno::EOPNOTSUPP)); + // A socket with no algorithm has no key and no operation socket. + assert_eq!(s.set_option(SOL_ALG, ALG_SET_KEY, b"k").err(), Some(Errno::ENOPROTOOPT)); + assert_eq!(s.accept(false).err(), Some(Errno::EINVAL)); + assert_eq!(send(&*s, b"x", 0).err(), Some(Errno::EOPNOTSUPP)); + let a = addr("hash", "sha256"); + assert_eq!(s.bind(&NoPaths, &a[..NAME_OFFSET]).err(), Some(Errno::EINVAL)); + let mut bad = a.clone(); + bad[16] = 1; + assert_eq!(s.bind(&NoPaths, &bad).err(), Some(Errno::EINVAL)); + // `CRYPTO_ALG_KERN_DRIVER_ONLY` is the one flag that the mask can have. + let mut ok = a.clone(); + ok[20..24].copy_from_slice(&CRYPTO_ALG_KERN_DRIVER_ONLY.to_ne_bytes()); + s.bind(&NoPaths, &ok).unwrap(); + for (kind, name) in [("nope", "sha256"), ("hash", "md2"), ("hash", "cbc(aes)")] { + assert_eq!(s.bind(&NoPaths, &addr(kind, name)).err(), Some(Errno::ENOENT), "{name}"); + } + // A driver name and a nested HMAC are not in the allow list. + for name in ["sha256-generic", "hmac(hmac(sha256))", "hmac(sha256-generic)"] { + assert_eq!(s.bind(&NoPaths, &addr("hash", name)).err(), Some(Errno::ENOENT), "{name}"); + } + // The name ends at the last byte of the address. + let short = &a[..NAME_OFFSET + 5]; + assert_eq!(s.bind(&NoPaths, short).err(), Some(Errno::ENOENT)); + s.bind(&NoPaths, &a[..NAME_OFFSET + 7]).unwrap(); +} + +#[test] +fn hash_send_recv() { + let s = bound("hash", "sha256"); + let op = s.accept(false).unwrap(); + // No data gives the digest of no data. + assert_eq!(digest(&*op), EMPTY); + assert_eq!(send(&*op, b"abc", 0), Ok(3)); + assert_eq!(digest(&*op), ABC); + // The receive ended the digest. + assert_eq!(digest(&*op), EMPTY); + // Pieces with `MSG_MORE`, and an empty send that ends the digest. + assert_eq!(send(&*op, b"a", MSG_MORE), Ok(1)); + assert_eq!(send(&*op, b"b", MSG_MORE), Ok(1)); + assert_eq!(send(&*op, b"c", MSG_MORE), Ok(1)); + assert_eq!(send(&*op, b"", 0), Ok(0)); + assert_eq!(digest(&*op), ABC); + // A receive ends an open digest. + send(&*op, b"ab", MSG_MORE).unwrap(); + send(&*op, b"c", MSG_MORE).unwrap(); + assert_eq!(digest(&*op), ABC); + // A send without `MSG_MORE` drops the digest before. + send(&*op, b"xyz", 0).unwrap(); + send(&*op, b"abc", 0).unwrap(); + assert_eq!(digest(&*op), ABC); + // A short receive gives the start of the digest. + send(&*op, b"abc", 0).unwrap(); + let r = op.recv(4, 0, false).unwrap(); + assert_eq!((hex(&r.data), r.len, r.flags), (ABC[..8].into(), 4, MSG_TRUNC)); + // The bound socket cannot change while it has an operation socket. + assert_eq!(s.bind(&NoPaths, &addr("hash", "sha1")).err(), Some(Errno::EBUSY)); + assert_eq!(s.set_option(SOL_ALG, ALG_SET_KEY, b"k").err(), Some(Errno::EBUSY)); + assert_eq!(op.set_option(SOL_ALG, ALG_SET_KEY, b"k").err(), Some(Errno::EOPNOTSUPP)); + drop(op); + // A hash with no key refuses a key, as `shash_no_setkey` of Linux. + assert_eq!(s.set_option(SOL_ALG, ALG_SET_KEY, b"k").err(), Some(Errno::ENOSYS)); + s.bind(&NoPaths, &addr("hash", "sha1")).unwrap(); + let op = s.accept(false).unwrap(); + send(&*op, b"abc", 0).unwrap(); + assert_eq!(digest(&*op), "a9993e364706816aba3e25717850c26c9cd0d89d"); +} + +#[test] +fn hash_accept_copies_state() { + let s = bound("hash", "sha256"); + let op = s.accept(false).unwrap(); + send(&*op, b"ab", MSG_MORE).unwrap(); + let copy = op.accept(false).unwrap(); + send(&*op, b"c", 0).unwrap(); + send(&*copy, b"c", 0).unwrap(); + assert_eq!(digest(&*op), ABC); + assert_eq!(digest(&*copy), ABC); + // A closed digest is not copied. + let fresh = op.accept(false).unwrap(); + assert_eq!(digest(&*fresh), EMPTY); +} + +#[test] +fn hmac_key() { + let s = bound("hash", "hmac(sha256)"); + // An operation socket before the key has no key. + let early = s.accept(false).unwrap(); + assert_eq!(send(&*early, b"x", 0).err(), Some(Errno::ENOKEY)); + assert_eq!(early.recv(32, 0, false).err(), Some(Errno::ENOKEY)); + assert_eq!(early.accept(false).err(), Some(Errno::ENOKEY)); + // The bound socket can take a key while its operation sockets have none. + s.set_option(SOL_ALG, ALG_SET_KEY, b"Jefe").unwrap(); + send(&*early, b"what do ya want for nothing?", 0).unwrap(); + assert_eq!(digest(&*early), JEFE); + // Now the operation socket has the key, and the key cannot change. + assert_eq!(s.set_option(SOL_ALG, ALG_SET_KEY, b"x").err(), Some(Errno::EBUSY)); + let op = s.accept(false).unwrap(); + send(&*op, b"what do ya want for nothing?", 0).unwrap(); + assert_eq!(digest(&*op), JEFE); + drop((early, op)); + s.set_option(SOL_ALG, ALG_SET_KEY, b"other").unwrap(); + let op = s.accept(false).unwrap(); + send(&*op, b"what do ya want for nothing?", 0).unwrap(); + assert_ne!(digest(&*op), JEFE); +} diff --git a/net/rk-net/src/lib.rs b/net/rk-net/src/lib.rs index ce9636f..d8889d6 100644 --- a/net/rk-net/src/lib.rs +++ b/net/rk-net/src/lib.rs @@ -15,6 +15,7 @@ extern crate alloc; +pub mod alg; pub mod arp; pub mod buf; pub mod consts; diff --git a/sec/rk-crypto/src/modes.rs b/sec/rk-crypto/src/modes.rs index a021bc6..785f1b5 100644 --- a/sec/rk-crypto/src/modes.rs +++ b/sec/rk-crypto/src/modes.rs @@ -51,7 +51,7 @@ impl Block { /// `xts_init_crypt` of Linux. fn check(&self, len: usize) -> Result<()> { let ok = match self.mode { - Mode::Ecb | Mode::Cbc => len % BLOCK == 0, + Mode::Ecb | Mode::Cbc => len.is_multiple_of(BLOCK), Mode::Ctr => true, Mode::Xts => len >= BLOCK, }; @@ -164,7 +164,7 @@ impl Skcipher for Block { } if self.mode == Mode::Xts { // `xts_verify_key` of Linux: the two halves have the same size. - if key.len() % 2 != 0 { + if !key.len().is_multiple_of(2) { return Err(Errno::EINVAL); } let (a, b) = key.split_at(key.len() / 2); From c49bab24233155e42cce70d5a399d0bbe0387d66 Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:37:35 +0700 Subject: [PATCH 2/2] Merge the Unreleased sections of the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b65a6b5..4156625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,12 @@ This file records each release. The format follows [Keep a Changelog](https://ke - The eBPF verifier gives the result of kernel/bpf/verifier.c of Linux for all programs of bpf/test_verifier of the socket filter and cgroup program types. It prunes the paths with equal states at the jump targets and refuses a loop that does not end, it refines the bounds of a register after `BPF_JSET` and after a `BPF_JMP32` that compares as 64 bits, and it checks the alignment of the pointers and the size of each context field as Linux, with the errors and the log messages of Linux in the same order. `bpf(BPF_PROG_TEST_RUN)` runs a program on a packet or a context from user space. The kselftest boot test runs bpf/test_verifier, and tools/kselftest/bpf.md gives the result: 281 passed and 0 failed (#537). - The stateful expressions of the sets of nf_tables, as `nft_set_elem_expr_alloc` of Linux: `NFTA_SET_EXPR` and `NFTA_SET_EXPRESSIONS` of a set, `NFTA_SET_ELEM_EXPR` and `NFTA_SET_ELEM_EXPRESSIONS` of an element, and `NFTA_DYNSET_EXPR` and `NFTA_DYNSET_EXPRESSIONS` of `dynset`. The expressions are `counter`, `quota`, `limit` and `connlimit`, two at most. Each element has its own state: a copy of the expressions of the set, or the ones that it gives with the same types. A `lookup` and a `dynset` run the expressions of the element that they find. The `connlimit` expression of nft_connlimit.c counts the connections that connection tracking still has (#542). +- The `AF_ALG` sockets with the type `hash`, as crypto/af_alg.c and crypto/algif_hash.c of Linux. A socket binds to a hash of `rk-crypto` by its name, `setsockopt(SOL_ALG, ALG_SET_KEY)` sets the key of an HMAC, and `accept` gives a socket for one computation. A `send` with `MSG_MORE` adds data, and a `recv` gives the digest. An `accept` on the socket of a computation copies its state. As with `crypto.af_alg_restrict=1` of Linux, a name that is not in the allow list gives `ENOENT`, and a name that needs a capability gives `ENOENT` without `CAP_SYS_ADMIN` or `CAP_NET_ADMIN` (#541). ## [0.5.22] - 2026-11-12 ### Added -- The `AF_ALG` sockets with the type `hash`, as crypto/af_alg.c and crypto/algif_hash.c of Linux. A socket binds to a hash of `rk-crypto` by its name, `setsockopt(SOL_ALG, ALG_SET_KEY)` sets the key of an HMAC, and `accept` gives a socket for one computation. A `send` with `MSG_MORE` adds data, and a `recv` gives the digest. An `accept` on the socket of a computation copies its state. As with `crypto.af_alg_restrict=1` of Linux, a name that is not in the allow list gives `ENOENT`, and a name that needs a capability gives `ENOENT` without `CAP_SYS_ADMIN` or `CAP_NET_ADMIN` (#541). - The crypto API in the new crate `rk-crypto`, as crypto/ of Linux: the hashes SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512, the template `hmac`, the block cipher AES, and the templates `ecb`, `cbc`, `ctr` and `xts` with ciphertext stealing. The code is plain Rust. The unit tests use the vectors of crypto/testmgr.h of Linux (#541). - The event log of the firmware at `/sys/kernel/security/tpm0/binary_bios_measurements`, as drivers/char/tpm/eventlog of Linux. The kernel copies the log area of the ACPI `TPM2` table on x86-64, or the area of `linux,sml-base` and `linux,sml-size` of the device tree node of the TPM on arm64. The file has the first event and each next event of the crypto agile format, up to the first event that is not valid, as `tpm2_binary_bios_measurements_show` of Linux. On x86-64 the `tpm` boot test replays the log of SeaBIOS with `tpm2_eventlog`, and the SHA-256 PCRs of the log must be the PCRs of `tpm2_pcrread` (#541). - The flowtables of nf_tables, as nf_tables_api.c and nft_flow_offload.c of Linux: `NFT_MSG_NEWFLOWTABLE`, `NFT_MSG_GETFLOWTABLE`, `NFT_MSG_DELFLOWTABLE` and `NFT_MSG_DESTROYFLOWTABLE`, with the ingress hook, the priority, the flags and the list of devices, and the `flow_offload` expression in the forward hook. A device can be in one flowtable of a table only (`EEXIST`), a flowtable that a rule uses cannot be deleted (`EBUSY`), and a device that goes away leaves the flowtable. The packets do not take a fast path: `flow_offload` does not change them (#542).