From d851f9019bb069ad83b3151c48611b199506b89b Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Thu, 12 Nov 2026 22:11:40 +0700 Subject: [PATCH 1/3] AF_ALG: the skcipher sockets --- CHANGELOG.md | 1 + _typos.toml | 1 + net/rk-net/src/alg/hash.rs | 10 +- net/rk-net/src/alg/mod.rs | 13 +- net/rk-net/src/alg/skcipher.rs | 389 +++++++++++++++++++++++++++++++++ net/rk-net/src/alg/tests.rs | 120 ++++++++++ 6 files changed, 530 insertions(+), 4 deletions(-) create mode 100644 net/rk-net/src/alg/skcipher.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d2798d..b9c81f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ 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). +- The `AF_ALG` sockets with the type `skcipher`, as crypto/algif_skcipher.c of Linux, for `ecb(aes)`, `cbc(aes)`, `ctr(aes)` and `xts(aes)` of `rk-crypto`. A `sendmsg` gives the operation with `ALG_SET_OP` and the IV with `ALG_SET_IV` in control messages, and adds the data to the queue. A `recv` encrypts or decrypts the data of the queue in blocks. With `MSG_MORE`, the operation continues in the next `send`, and the IV goes from one `recv` to the next. libcryptsetup uses these sockets to open a LUKS2 key slot without dm-crypt (#541). ## [0.5.22] - 2026-11-12 diff --git a/_typos.toml b/_typos.toml index eb49f51..e850ebf 100644 --- a/_typos.toml +++ b/_typos.toml @@ -38,6 +38,7 @@ controllen = "controllen" # msg_controllen of struct msghdr BA = "BA" # BARs, PCI base address registers, split by the checker exportfs = "exportfs" # the NFS export code of Linux and the group of fstests IIF = "IIF" # RTA_IIF of rtnetlink, the input interface of a route +ede = "ede" # des3_ede, the triple DES of Linux (encrypt, decrypt, encrypt) OIF = "OIF" # RTA_OIF of rtnetlink, the output interface of a route Parm = "Parm" # InParmProbs of /proc/net/snmp, the ICMP parameter problems Ofo = "Ofo" # OfoPruned of /proc/net/netstat, the out of order queue of TCP diff --git a/net/rk-net/src/alg/hash.rs b/net/rk-net/src/alg/hash.rs index 9d44f48..a9476b4 100644 --- a/net/rk-net/src/alg/hash.rs +++ b/net/rk-net/src/alg/hash.rs @@ -80,8 +80,9 @@ impl HashSocket { return Ok(()); } self.bound.check_key(|tfm| { - let Tfm::Hash(h) = tfm; - ctx.hash = h.box_clone(); + if let Tfm::Hash(h) = tfm { + ctx.hash = h.box_clone(); + } })?; ctx.nokey = false; Ok(()) @@ -122,7 +123,10 @@ impl Socket for HashSocket { self.check_key(&mut ctx)?; ctx.more.then(|| ctx.hash.box_clone()) }; - let Child::Hash(child) = self.bound.accept()?; + // The bound socket of a hash socket has a hash. + let Child::Hash(child) = self.bound.accept()? else { + return Err(Errno::EINVAL); + }; if let Some(hash) = state { let mut c = child.ctx.lock(); c.hash = hash; diff --git a/net/rk-net/src/alg/mod.rs b/net/rk-net/src/alg/mod.rs index 216dd84..662aa90 100644 --- a/net/rk-net/src/alg/mod.rs +++ b/net/rk-net/src/alg/mod.rs @@ -5,7 +5,8 @@ //! `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. +//! crypto/algif_hash.c of Linux, and the module `skcipher` has the operation sockets of the type +//! `skcipher`, as crypto/algif_skcipher.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` @@ -17,6 +18,7 @@ //! key of the bound socket cannot change while it has an operation socket with a key. mod hash; +mod skcipher; #[cfg(test)] mod tests; @@ -84,6 +86,7 @@ fn check_restriction(name: &str, list: AllowList) -> Result<()> { /// The algorithm of a bound socket, as the `private` of a `struct alg_sock` of Linux. enum Tfm { Hash(Box), + Skcipher(Box), } impl Tfm { @@ -91,6 +94,7 @@ impl Tfm { fn bind(kind: &str, name: &str) -> Result { match kind { "hash" => hash::bind(name).map(Self::Hash), + "skcipher" => skcipher::bind(name).map(Self::Skcipher), _ => Err(Errno::ENOENT), } } @@ -98,12 +102,14 @@ impl Tfm { fn needs_key(&self) -> bool { match self { Self::Hash(h) => h.needs_key(), + Self::Skcipher(c) => c.needs_key(), } } fn set_key(&mut self, key: &[u8]) -> Result<()> { match self { Self::Hash(h) => h.set_key(key), + Self::Skcipher(c) => c.set_key(key), } } } @@ -123,12 +129,14 @@ struct Bound(SpinLock); /// A new operation socket. enum Child { Hash(Arc), + Skcipher(Arc), } impl Child { fn socket(self) -> Arc { match self { Self::Hash(s) => s, + Self::Skcipher(s) => s, } } } @@ -144,6 +152,9 @@ impl Bound { let nokey = tfm.needs_key(); let child = match tfm { Tfm::Hash(h) => Child::Hash(hash::HashSocket::new(self.clone(), h.box_clone(), nokey)), + Tfm::Skcipher(c) => { + Child::Skcipher(skcipher::SkcipherSocket::new(self.clone(), c.box_clone(), nokey)) + } }; s.children += 1; if nokey { diff --git a/net/rk-net/src/alg/skcipher.rs b/net/rk-net/src/alg/skcipher.rs new file mode 100644 index 0000000..c3bb956 --- /dev/null +++ b/net/rk-net/src/alg/skcipher.rs @@ -0,0 +1,389 @@ +//! The operation sockets of the type `skcipher`, as crypto/algif_skcipher.c of Linux. +//! +//! A send adds data to the queue of the socket. Its control messages of the level `SOL_ALG` set +//! the operation (`ALG_SET_OP`) and the IV (`ALG_SET_IV`). A send with `MSG_MORE` tells that more +//! data comes. A receive encrypts or decrypts the data of the queue and gives the result. While +//! more data comes, a receive takes only full blocks, and the IV of the next receive goes on from +//! the data before, as `CRYPTO_SKCIPHER_REQ_NOTFINAL` of Linux. A receive waits for data if the +//! socket has none yet, or if more data comes and the queue has less than one block. After a +//! receive takes all the data of an operation that ended, the next receive waits for a new send. +//! +//! The queue holds at most the size of the send buffer, as `af_alg_sndbuf` of Linux. A send +//! waits until a receive takes data from a full queue. + +use super::{AF_ALG, AllowList, Bound, SOL_ALG, Tfm, get_socket_option}; +use super::{ALG_SET_AEAD_ASSOCLEN, ALG_SET_IV, ALG_SET_OP}; +use crate::consts::{MSG_DONTWAIT, MSG_MORE, SOCK_SEQPACKET}; +use crate::options::{Options, Timeout}; +use crate::{Message, PathOps, Received, Socket, next_ino, wait_for}; +use alloc::boxed::Box; +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; +use core::any::Any; +use core::sync::atomic::{AtomicU64, Ordering}; +use rk_abi::Errno; +use rk_abi::native::poll::{POLLIN, POLLOUT, POLLRDNORM, POLLWRBAND, POLLWRNORM}; +use rk_abi::native::socket::SOL_SOCKET; +use rk_crypto::Skcipher; +use rk_frame::addr::PAGE_SIZE; +use rk_frame::sync::SpinLock; +use rk_vfs::inode::Result; +use rk_vfs::wait::{PollTable, WaitQueue}; + +/// The operation of `ALG_SET_OP` that decrypts. +pub(super) const ALG_OP_DECRYPT: u32 = 0; +/// The operation of `ALG_SET_OP` that encrypts. +pub(super) const ALG_OP_ENCRYPT: u32 = 1; + +/// The ciphers that a program can bind, as `skcipher_allowlist` of Linux. +const ALLOW: AllowList = &[ + ("adiantum(xchacha12,aes)", true), + ("adiantum(xchacha20,aes)", true), + ("cbc(aes)", false), + ("cbc(des)", false), + ("cbc(des3_ede)", false), + ("cbc(paes)", false), + ("ctr(aes)", false), + ("ecb(aes)", false), + ("ecb(des)", false), + ("hctr2(aes)", true), + ("xts(aes)", true), + ("xts(camellia)", true), + ("xts(serpent)", true), + ("xts(twofish)", true), +]; + +/// Finds a cipher, as `skcipher_bind` of Linux. +pub(super) fn bind(name: &str) -> Result> { + super::check_restriction(name, ALLOW)?; + rk_crypto::alloc_skcipher(name) +} + +/// The state of an operation socket, as `struct af_alg_ctx` of Linux. +struct Ctx { + cipher: Box, + /// The data that waits for a receive. + queue: VecDeque, + /// The IV of the next receive. + iv: Vec, + /// A send came. + init: bool, + /// The last send had `MSG_MORE`. + more: bool, + /// Encrypt, not decrypt. + enc: bool, + /// A send waits for space in the queue. + write: bool, + /// The socket has no key yet. + nokey: bool, +} + +/// The settings of the control messages of one send, as `struct af_alg_control` of Linux. +#[derive(Default)] +struct Control { + iv: Option>, + op: u32, +} + +/// Reads the control messages of the level `SOL_ALG`, as `af_alg_cmsg_send` of Linux. +fn control(msg: &Message) -> Result { + let mut con = Control::default(); + let word = |data: &[u8]| -> Result { + let (w, _) = data.split_first_chunk::<4>().ok_or(Errno::EINVAL)?; + Ok(u32::from_ne_bytes(*w)) + }; + for (level, kind, data) in &msg.control { + if *level != SOL_ALG { + continue; + } + match *kind { + ALG_SET_IV => { + let len = word(data)? as usize; + let iv = data.get(4..).and_then(|d| d.get(..len)).ok_or(Errno::EINVAL)?; + con.iv = Some(iv.to_vec()); + } + ALG_SET_OP => con.op = word(data)?, + ALG_SET_AEAD_ASSOCLEN => { + if word(data)? >= 0x8000_0000 { + return Err(Errno::EINVAL); + } + } + _ => return Err(Errno::EINVAL), + } + } + Ok(con) +} + +/// An operation socket of the type `skcipher`. +pub(super) struct SkcipherSocket { + ino: u64, + bound: Arc, + ctx: SpinLock, + options: SpinLock, + wait: Arc, + events: AtomicU64, +} + +impl SkcipherSocket { + pub(super) fn new(bound: Arc, cipher: Box, nokey: bool) -> Arc { + let iv = vec![0u8; cipher.iv_size()]; + Arc::new(Self { + ino: next_ino(), + bound, + ctx: SpinLock::new(Ctx { + cipher, + queue: VecDeque::new(), + iv, + init: false, + more: false, + enc: false, + write: false, + nokey, + }), + options: SpinLock::new(Options::default()), + wait: Arc::new(WaitQueue::new()), + events: AtomicU64::new(0), + }) + } + + /// A socket with no key takes the key of the bound socket, as `skcipher_check_key` of Linux. + fn check_key(&self) -> Result<()> { + let mut ctx = self.ctx.lock(); + if !ctx.nokey { + return Ok(()); + } + self.bound.check_key(|tfm| { + if let Tfm::Skcipher(c) = tfm { + ctx.cipher = c.box_clone(); + } + })?; + ctx.nokey = false; + Ok(()) + } + + /// The free space of the queue, as `af_alg_sndbuf` of Linux. + fn space(&self, ctx: &Ctx) -> usize { + let buf = (self.options.lock().sndbuf as usize & !(PAGE_SIZE - 1)).max(PAGE_SIZE); + buf.saturating_sub(ctx.queue.len()) + } + + /// The size of the receive buffer, as `af_alg_rcvbuf` of Linux with nothing in use. + fn rcvbuf(&self) -> usize { + (self.options.lock().rcvbuf as usize & !(PAGE_SIZE - 1)).max(PAGE_SIZE) + } + + fn wake(&self) { + self.events.fetch_add(1, Ordering::Relaxed); + self.wait.wake_all(); + } + + /// Adds data to the queue, as `af_alg_sendmsg` of Linux. + fn send_data(&self, msg: &Message, flags: u32, nonblock: bool) -> Result { + let con = if msg.control.is_empty() && msg.rights.is_empty() && msg.cred.is_none() { + None + } else { + Some(control(msg)?) + }; + let enc = match con.as_ref().map(|c| c.op) { + None | Some(ALG_OP_DECRYPT) => false, + Some(ALG_OP_ENCRYPT) => true, + Some(_) => return Err(Errno::EINVAL), + }; + let mut ctx = self.ctx.lock(); + if let Some(iv) = con.as_ref().and_then(|c| c.iv.as_ref()) + && iv.len() != ctx.cipher.iv_size() + { + return Err(Errno::EINVAL); + } + if ctx.write { + return Err(Errno::EBUSY); + } + // The data of an operation that ended waits for a receive. + if ctx.init && !ctx.more && !ctx.queue.is_empty() { + return Err(Errno::EINVAL); + } + ctx.init = true; + if let Some(con) = con { + ctx.enc = enc; + if let Some(iv) = con.iv { + ctx.iv = iv; + } + } + let data = &msg.data; + let mut copied = 0; + let mut err = None; + while copied < data.len() { + let space = self.space(&ctx); + if space < PAGE_SIZE { + // `af_alg_wait_for_wmem` of Linux: wait for a receive. + if nonblock || flags & MSG_DONTWAIT != 0 { + err = Some(Errno::EAGAIN); + break; + } + ctx.write = true; + drop(ctx); + let r = wait_for(&self.wait, Timeout::default(), || { + let c = self.ctx.lock(); + Ok((self.space(&c) >= PAGE_SIZE).then_some(())) + }); + ctx = self.ctx.lock(); + ctx.write = false; + if let Err(e) = r { + err = Some(e); + break; + } + continue; + } + let n = space.min(data.len() - copied); + ctx.queue.extend(&data[copied..copied + n]); + copied += n; + } + if err.is_none() { + ctx.more = flags & MSG_MORE != 0; + } + let used = !ctx.queue.is_empty(); + drop(ctx); + if used { + self.wake(); + } + match err { + Some(e) if copied == 0 => Err(e), + _ => Ok(copied), + } + } + + /// One operation of a receive, as `_skcipher_recvmsg` of Linux. Gives the result, at most + /// `len` bytes. + fn recv_once(&self, len: usize, wait: bool) -> Result> { + let mut ctx = self.ctx.lock(); + let bs = ctx.cipher.chunk_size(); + if !ctx.init || (ctx.more && ctx.queue.len() < bs) { + // `af_alg_wait_for_data` of Linux. + if !wait { + return Err(Errno::EAGAIN); + } + drop(ctx); + wait_for(&self.wait, Timeout::default(), || { + let c = self.ctx.lock(); + Ok((c.init && (!c.more || c.queue.len() >= bs)).then_some(())) + })?; + ctx = self.ctx.lock(); + } + let used = ctx.queue.len(); + let mut n = len.min(used).min(self.rcvbuf()); + // While more data comes, take only full blocks. + if ctx.more || n < used { + n -= n % bs; + if n == 0 { + return Err(Errno::EINVAL); + } + } + let mut data: Vec = ctx.queue.drain(..n).collect(); + // `af_alg_pull_tsgl` of Linux: an operation that ended needs a new send. + if ctx.queue.is_empty() { + ctx.init = ctx.more; + } + let ctx = &mut *ctx; + if ctx.enc { + ctx.cipher.encrypt(&mut ctx.iv, &mut data)?; + } else { + ctx.cipher.decrypt(&mut ctx.iv, &mut data)?; + } + Ok(data) + } +} + +impl Drop for SkcipherSocket { + fn drop(&mut self) { + let nokey = self.ctx.lock().nokey; + self.bound.release(nokey); + } +} + +impl Socket for SkcipherSocket { + fn family(&self) -> u32 { + AF_ALG + } + + fn kind(&self) -> u32 { + SOCK_SEQPACKET + } + + fn ino(&self) -> u64 { + self.ino + } + + fn send(&self, _: &dyn PathOps, msg: Message, flags: u32, nonblock: bool) -> Result { + self.check_key()?; + self.send_data(&msg, flags, nonblock) + } + + /// Encrypts or decrypts the data of the queue, as `skcipher_recvmsg` of Linux. The receive + /// does operations until it has `len` bytes. An error after some data gives the data. As + /// Linux, a blocking receive of more bytes than the operation has waits for a new send. + fn recv(&self, len: usize, flags: u32, nonblock: bool) -> Result { + self.check_key()?; + let wait = !nonblock && flags & MSG_DONTWAIT == 0; + let mut out = Vec::new(); + let mut result = Ok(()); + while out.len() < len { + match self.recv_once(len - out.len(), wait) { + Ok(d) if d.is_empty() => break, + Ok(d) => out.extend_from_slice(&d), + Err(e) => { + result = Err(e); + break; + } + } + } + self.wake(); + if let Err(e) = result + && out.is_empty() + { + return Err(e); + } + let n = out.len(); + Ok(Received { data: out, len: n, ..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) + } + + /// The events, as `af_alg_poll` of Linux. + fn poll(&self) -> u32 { + let ctx = self.ctx.lock(); + let read = if !ctx.more || !ctx.queue.is_empty() { POLLIN | POLLRDNORM } else { 0 }; + let write = + if self.space(&ctx) >= PAGE_SIZE { POLLOUT | POLLWRNORM | POLLWRBAND } else { 0 }; + read | write + } + + fn poll_wait(&self, table: &mut PollTable) { + table.add(&self.wait); + } + + fn event_count(&self) -> u64 { + self.events.load(Ordering::Relaxed) + } + + 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 index 5b6794e..3be492a 100644 --- a/net/rk-net/src/alg/tests.rs +++ b/net/rk-net/src/alg/tests.rs @@ -4,6 +4,7 @@ use super::*; use crate::NoPaths; use crate::consts::{MSG_MORE, MSG_TRUNC, SOCK_STREAM}; use alloc::vec; +use rk_abi::native::poll::{POLLIN, POLLOUT}; const ABC: &str = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; const EMPTY: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; @@ -151,3 +152,122 @@ fn hmac_key() { send(&*op, b"what do ya want for nothing?", 0).unwrap(); assert_ne!(digest(&*op), JEFE); } + +/// The key, the IV and the text of NIST SP 800-38A F.2.1, CBC-AES128. +const NIST_KEY: &str = "2b7e151628aed2a6abf7158809cf4f3c"; +const NIST_IV: &str = "000102030405060708090a0b0c0d0e0f"; +const NIST_PTEXT: &str = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"; +const NIST_CTEXT: &str = "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2"; + +fn unhex(s: &str) -> Vec { + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect() +} + +/// A send with the control messages of an operation: `op` and, if not empty, the IV. +fn send_op(s: &dyn Socket, data: &[u8], op: u32, iv: &[u8], flags: u32) -> Result { + let mut control = vec![(SOL_ALG, ALG_SET_OP, op.to_ne_bytes().to_vec())]; + if !iv.is_empty() { + let mut v = (iv.len() as u32).to_ne_bytes().to_vec(); + v.extend_from_slice(iv); + control.push((SOL_ALG, ALG_SET_IV, v)); + } + let msg = Message { data: data.to_vec(), control, ..Message::default() }; + s.send(&NoPaths, msg, flags, false) +} + +fn cipher_op(name: &str, key: &[u8]) -> Arc { + let s = bound("skcipher", name); + s.set_option(SOL_ALG, ALG_SET_KEY, key).unwrap(); + s.accept(false).unwrap() +} + +#[test] +fn skcipher_cbc() { + let (key, iv) = (unhex(NIST_KEY), unhex(NIST_IV)); + let (ptext, ctext) = (unhex(NIST_PTEXT), unhex(NIST_CTEXT)); + let op = cipher_op("cbc(aes)", &key); + assert_eq!(send_op(&*op, &ptext, skcipher::ALG_OP_ENCRYPT, &iv, 0), Ok(32)); + // A blocking receive of more than the data would wait for a new send, as Linux, so the + // receives here ask for the length of the data. + assert_eq!(op.recv(32, 0, false).unwrap().data, ctext); + // The operation ended, so a receive waits for a new send. + assert_eq!(op.recv(64, 0, true).err(), Some(Errno::EAGAIN)); + // A decryption in two sends: the IV of the second block comes from the first block. + send_op(&*op, &ctext[..16], skcipher::ALG_OP_DECRYPT, &iv, MSG_MORE).unwrap(); + assert_eq!(op.recv(16, 0, false).unwrap().data, ptext[..16]); + send(&*op, &ctext[16..], 0).unwrap(); + assert_eq!(op.recv(16, 0, false).unwrap().data, ptext[16..]); + // A receive in pieces that are smaller than the data. + send_op(&*op, &ptext, skcipher::ALG_OP_ENCRYPT, &iv, 0).unwrap(); + assert_eq!(op.recv(20, 0, false).unwrap().data, ctext[..16]); + assert_eq!(op.recv(16, 0, false).unwrap().data, ctext[16..]); + // A send with no control message keeps the operation, and the IV goes on from the last + // block. + send(&*op, &ptext, 0).unwrap(); + let mut c = rk_crypto::alloc_skcipher("cbc(aes)").unwrap(); + c.set_key(&key).unwrap(); + let (mut next, mut want) = (ctext[16..].to_vec(), ptext.clone()); + c.encrypt(&mut next, &mut want).unwrap(); + assert_eq!(op.recv(32, 0, false).unwrap().data, want); +} + +#[test] +fn skcipher_errors() { + let (key, iv) = (unhex(NIST_KEY), unhex(NIST_IV)); + let s = bound("skcipher", "cbc(aes)"); + assert_eq!(s.bind(&NoPaths, &addr("skcipher", "cbc(aes-generic)")).err(), Some(Errno::ENOENT)); + assert_eq!(s.bind(&NoPaths, &addr("skcipher", "sha256")).err(), Some(Errno::ENOENT)); + assert_eq!(s.set_option(SOL_ALG, ALG_SET_KEY, &key[..15]).err(), Some(Errno::EINVAL)); + // 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)); + s.set_option(SOL_ALG, ALG_SET_KEY, &key).unwrap(); + let op = s.accept(false).unwrap(); + // A receive with no send waits. + assert_eq!(op.recv(16, 0, true).err(), Some(Errno::EAGAIN)); + assert_eq!(op.accept(false).err(), Some(Errno::EOPNOTSUPP)); + // A bad operation, a bad IV and an unknown control message. + assert_eq!(send_op(&*op, &[0; 16], 2, &iv, 0).err(), Some(Errno::EINVAL)); + assert_eq!(send_op(&*op, &[0; 16], 1, &iv[..8], 0).err(), Some(Errno::EINVAL)); + let msg = Message { + data: vec![0; 16], + control: vec![(SOL_ALG, 9, vec![0; 4])], + ..Message::default() + }; + assert_eq!(op.send(&NoPaths, msg, 0, false).err(), Some(Errno::EINVAL)); + // The data of an operation that ended waits for a receive. + send_op(&*op, &[0; 16], 1, &iv, 0).unwrap(); + assert_eq!(send(&*op, &[0; 16], 0).err(), Some(Errno::EINVAL)); + op.recv(16, 0, false).unwrap(); + // A length that is not a multiple of the block, as af_alg05 of LTP. + send_op(&*op, &[0; 15], 0, &iv, 0).unwrap(); + assert_eq!(op.recv(16, 0, false).err(), Some(Errno::EINVAL)); + // While more data comes, a receive waits for a full block. + let op = s.accept(false).unwrap(); + assert_eq!(op.poll() & (POLLIN | POLLOUT), POLLIN | POLLOUT); + send_op(&*op, &[0; 8], 1, &iv, MSG_MORE).unwrap(); + assert_eq!(op.recv(16, 0, true).err(), Some(Errno::EAGAIN)); + send(&*op, &[0; 8], MSG_MORE).unwrap(); + assert_eq!(op.recv(16, 0, false).unwrap().data.len(), 16); + // More data comes and the queue is empty: the socket is not readable. + assert_eq!(op.poll() & (POLLIN | POLLOUT), POLLOUT); +} + +#[test] +fn skcipher_xts() { + let key: Vec = (0..32).collect(); + let iv = [7u8; 16]; + let op = cipher_op("xts(aes)", &key); + let mut c = rk_crypto::alloc_skcipher("xts(aes)").unwrap(); + c.set_key(&key).unwrap(); + // Whole blocks, and lengths with ciphertext stealing. + for len in [16, 17, 32, 100] { + let data: Vec = (0..len as u8).collect(); + let mut want = data.clone(); + c.encrypt(&mut iv.clone(), &mut want).unwrap(); + send_op(&*op, &data, skcipher::ALG_OP_ENCRYPT, &iv, 0).unwrap(); + assert_eq!(op.recv(len, 0, false).unwrap().data, want, "{len}"); + send_op(&*op, &want, skcipher::ALG_OP_DECRYPT, &iv, 0).unwrap(); + assert_eq!(op.recv(len, 0, false).unwrap().data, data, "{len}"); + } +} From c50ba669bf719527fbc5dab9d9c5fb7cfed7f734 Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Thu, 12 Nov 2026 22:12:20 +0700 Subject: [PATCH 2/3] 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 b9c81f2..fc19316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This file records each release. The format follows [Keep a Changelog](https://ke - fanotify has `FAN_REPORT_MNT`. A group with this flag marks a mount namespace with `FAN_MARK_MNTNS` and gets `FAN_MNT_ATTACH` and `FAN_MNT_DETACH` when a mount of the namespace is attached, moved or unmounted. Each event has a `FAN_EVENT_INFO_TYPE_MNT` record with the unique mount ID, and the errors and the record layout are the same as in Linux. The `init` boot test checks the errors, the fdinfo line and the events of mount, move and umount. (#539) - The `sdbpf` boot test runs the unit tests test-bpf-devices, test-bpf-firewall and test-bpf-foreign-programs of systemd. They attach cgroup programs with `BPF_PROG_ATTACH`, `BPF_PROG_DETACH`, `BPF_PROG_QUERY` and `BPF_LINK_CREATE`, and all three pass. `RK_SYSTEMD_PACKAGES` of tools/systemd/build.sh adds packages to the systemd tree, and this test needs `systemd-tests` and `iputils-ping` (#537). +- The `AF_ALG` sockets with the type `skcipher`, as crypto/algif_skcipher.c of Linux, for `ecb(aes)`, `cbc(aes)`, `ctr(aes)` and `xts(aes)` of `rk-crypto`. A `sendmsg` gives the operation with `ALG_SET_OP` and the IV with `ALG_SET_IV` in control messages, and adds the data to the queue. A `recv` encrypts or decrypts the data of the queue in blocks. With `MSG_MORE`, the operation continues in the next `send`, and the IV goes from one `recv` to the next. libcryptsetup uses these sockets to open a LUKS2 key slot without dm-crypt (#541). ### Fixed @@ -27,7 +28,6 @@ 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). -- The `AF_ALG` sockets with the type `skcipher`, as crypto/algif_skcipher.c of Linux, for `ecb(aes)`, `cbc(aes)`, `ctr(aes)` and `xts(aes)` of `rk-crypto`. A `sendmsg` gives the operation with `ALG_SET_OP` and the IV with `ALG_SET_IV` in control messages, and adds the data to the queue. A `recv` encrypts or decrypts the data of the queue in blocks. With `MSG_MORE`, the operation continues in the next `send`, and the IV goes from one `recv` to the next. libcryptsetup uses these sockets to open a LUKS2 key slot without dm-crypt (#541). ## [0.5.22] - 2026-11-12 From 367ebb7efe0d1c8aa6a146e7652a99ed4ea36c4c Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:21:48 +0700 Subject: [PATCH 3/3] Merge the Unreleased sections of the changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc19316..679d73a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,16 @@ This file records each release. The format follows [Keep a Changelog](https://ke ## Unreleased +### Added + +- The `AF_ALG` sockets with the type `skcipher`, as crypto/algif_skcipher.c of Linux, for `ecb(aes)`, `cbc(aes)`, `ctr(aes)` and `xts(aes)` of `rk-crypto`. A `sendmsg` gives the operation with `ALG_SET_OP` and the IV with `ALG_SET_IV` in control messages, and adds the data to the queue. A `recv` encrypts or decrypts the data of the queue in blocks. With `MSG_MORE`, the operation continues in the next `send`, and the IV goes from one `recv` to the next. libcryptsetup uses these sockets to open a LUKS2 key slot without dm-crypt (#541). + ## [0.5.24] - 2026-11-12 ### Added - fanotify has `FAN_REPORT_MNT`. A group with this flag marks a mount namespace with `FAN_MARK_MNTNS` and gets `FAN_MNT_ATTACH` and `FAN_MNT_DETACH` when a mount of the namespace is attached, moved or unmounted. Each event has a `FAN_EVENT_INFO_TYPE_MNT` record with the unique mount ID, and the errors and the record layout are the same as in Linux. The `init` boot test checks the errors, the fdinfo line and the events of mount, move and umount. (#539) - The `sdbpf` boot test runs the unit tests test-bpf-devices, test-bpf-firewall and test-bpf-foreign-programs of systemd. They attach cgroup programs with `BPF_PROG_ATTACH`, `BPF_PROG_DETACH`, `BPF_PROG_QUERY` and `BPF_LINK_CREATE`, and all three pass. `RK_SYSTEMD_PACKAGES` of tools/systemd/build.sh adds packages to the systemd tree, and this test needs `systemd-tests` and `iputils-ping` (#537). -- The `AF_ALG` sockets with the type `skcipher`, as crypto/algif_skcipher.c of Linux, for `ecb(aes)`, `cbc(aes)`, `ctr(aes)` and `xts(aes)` of `rk-crypto`. A `sendmsg` gives the operation with `ALG_SET_OP` and the IV with `ALG_SET_IV` in control messages, and adds the data to the queue. A `recv` encrypts or decrypts the data of the queue in blocks. With `MSG_MORE`, the operation continues in the next `send`, and the IV goes from one `recv` to the next. libcryptsetup uses these sockets to open a LUKS2 key slot without dm-crypt (#541). ### Fixed