Add testcases for rekeying psp connections. Tests include coverage for different cases where rx-assoc and tx-assoc should fail based on mismatched psp version and psp state.
Add rpc endpoints for tx-assoc, rx-assoc, and key-rotate netlink calls to the psp_responder. Signed-off-by: Daniel Zahka <[email protected]> --- v2: - add tests for rekeys rejected due to psp state and version mismatch - add test for rx rekey rejected without a device key rotation - add rx-assoc, tx-assoc, and key-rotate rpcs to psp_responder --- tools/testing/selftests/drivers/net/psp.py | 273 ++++++++++++++++++++- .../testing/selftests/drivers/net/psp_responder.c | 111 +++++++++ 2 files changed, 382 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 71eeade62ad2..af67c9e8c40f 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -28,6 +28,10 @@ from lib.py import ip TCP_ULP = 31 +_PSP_MAX_KEY_LEN = 32 +_PSP_ASSOC_MSG = f'!IB3x{_PSP_MAX_KEY_LEN}s' + + def _get_outq(s): one = b'\0' * 4 outq = fcntl.ioctl(s.fileno(), termios.TIOCOUTQ, one) @@ -80,6 +84,36 @@ def _close_psp_conn(cfg, s): _close_conn(cfg, s) +def _recv_all(s, target): + data = b'' + while len(data) < target: + chunk = s.recv(target - len(data)) + if not chunk: + raise KsftFailEx(f"peer closed after {len(data)} of {target} bytes") + data += chunk + return data + + +def _psp_key_len(version): + return 16 if version in (0, 2) else 32 + + +def _remote_rx_assoc(cfg): + _send_with_ack(cfg, b'rx assoc\0') + msg = _recv_all(cfg.comm_sock, struct.calcsize(_PSP_ASSOC_MSG)) + spi, version, key = struct.unpack(_PSP_ASSOC_MSG, msg) + return version, {'spi': spi, 'key': key[:_psp_key_len(version)]} + + +def _remote_tx_assoc(cfg, version, rx): + msg = struct.pack(_PSP_ASSOC_MSG, rx['spi'], version, rx['key']) + _send_with_ack(cfg, b'tx assoc\0' + msg) + + +def _remote_key_rotate(cfg): + _send_with_ack(cfg, b'key rotate\0') + + def _spi_xchg(s, rx): s.send(struct.pack('I', rx['spi']) + rx['key']) tx = s.recv(4 + len(rx['key'])) @@ -130,6 +164,41 @@ def _check_data_outq(s, exp_len, force_wait=False): ksft_eq(outq, exp_len) +def _recv_careful(s, target, timeout=2): + """Read exactly target bytes, tolerating short reads""" + data = b'' + end = time.monotonic() + timeout + while time.monotonic() < end: + try: + data += s.recv(target - len(data), socket.MSG_DONTWAIT) + if len(data) == target: + return data + except BlockingIOError: + time.sleep(0.001) + raise KsftFailEx(f"short read, got {len(data)} of {target} bytes") + + +def _req_echo(cfg, s): + """Ask the peer to echo, and check the reply arrives intact""" + _send_with_ack(cfg, b'data echo\0') + ksft_eq(_recv_careful(s, 5), b'echo\0') + + +def _psp_txrx(cfg, s, rounds, sent=0): + """Send data both ways, and return the total bytes sent to the peer""" + sent += _send_careful(cfg, s, rounds) + _check_data_rx(cfg, sent) + _req_echo(cfg, s) + return sent + + +def _require_version(cfg, version): + """Skip the test unless the device supports the given PSP version""" + name = cfg.pspnl.consts["version"].entries_by_val[version].name + if name not in cfg.psp_info['psp-versions-cap']: + raise KsftSkipEx("PSP version not supported", name) + + def _get_stat(cfg, key): return cfg.pspnl.get_stats({'dev-id': cfg.psp_dev_id})[key] @@ -489,6 +558,204 @@ def _data_basic_send(cfg, version, ipver): _close_psp_conn(cfg, s) +def _rekey_rx(cfg, s, version, sent): + """Rekey the Rx direction, running traffic after each step""" + rx_assoc = cfg.pspnl.rx_assoc({"version": version, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + sent = _psp_txrx(cfg, s, 1, sent) + + _remote_tx_assoc(cfg, version, rx_assoc['rx-key']) + return _psp_txrx(cfg, s, 1, sent) + + +def _rekey_tx(cfg, s, sent): + """Rekey the Tx direction, running traffic after each step""" + version, tx = _remote_rx_assoc(cfg) + sent = _psp_txrx(cfg, s, 1, sent) + + cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, + "version": version, + "tx-key": tx, + "sock-fd": s.fileno()}) + return _psp_txrx(cfg, s, 1, sent) + + +def rekey_rx_incomplete(cfg): + """Test rejecting Rx rekey until the PSP connection is established""" + psp_ver = 0 + _init_psp_dev(cfg) + + with _make_lo_conn() as s: + assoc = cfg.pspnl.rx_assoc({"version": psp_ver, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + + # Reject rekey before Tx state is configured. + with ksft_raises(NlError) as cm: + cfg.pspnl.rx_assoc({"version": psp_ver, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + ksft_eq(cm.exception.nl_msg.error, -errno.EBUSY) + + cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, + "version": psp_ver, + "tx-key": assoc['rx-key'], + "sock-fd": s.fileno()}) + + # Reject rekey until authenticated PSP traffic has been received. + with ksft_raises(NlError) as cm: + cfg.pspnl.rx_assoc({"version": psp_ver, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + ksft_eq(cm.exception.nl_msg.error, -errno.EBUSY) + + +def rekey_tx_incomplete(cfg): + """Test rejecting Tx rekey until the PSP connection is established""" + psp_ver = 0 + _init_psp_dev(cfg) + + with _make_lo_conn() as s: + assoc = cfg.pspnl.rx_assoc({"version": psp_ver, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + + cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, + "version": psp_ver, + "tx-key": assoc['rx-key'], + "sock-fd": s.fileno()}) + + # Reject rekey until authenticated PSP traffic has been received. + with ksft_raises(NlError) as cm: + cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, + "version": psp_ver, + "tx-key": assoc['rx-key'], + "sock-fd": s.fileno()}) + ksft_eq(cm.exception.nl_msg.error, -errno.EBUSY) + + +def rekey_rx_same_gen(cfg): + """Test rejecting an Rx rekey without an intervening key rotation""" + _init_psp_dev(cfg) + + s = _establish_psp_conn(cfg, 0) + try: + _psp_txrx(cfg, s, 1) + + with ksft_raises(NlError) as cm: + cfg.pspnl.rx_assoc({"version": 0, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL) + + cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) + cfg.pspnl.rx_assoc({"version": 0, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + finally: + _close_psp_conn(cfg, s) + + +def rekey_rx_version_mismatch(cfg): + """Test rejecting an Rx rekey whose version does not match socket state""" + _init_psp_dev(cfg) + _require_version(cfg, 1) + + s = _establish_psp_conn(cfg, 0) + try: + _psp_txrx(cfg, s, 1) + + cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) + + with ksft_raises(NlError) as cm: + cfg.pspnl.rx_assoc({"dev-id": cfg.psp_dev_id, + "version": 1, + "sock-fd": s.fileno()}) + ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL) + finally: + _close_psp_conn(cfg, s) + + +def rekey_tx_version_mismatch(cfg): + """Test rejecting a Tx rekey whose version does not match socket state""" + _init_psp_dev(cfg) + _require_version(cfg, 1) + + s = _establish_psp_conn(cfg, 0) + try: + _psp_txrx(cfg, s, 1) + + with ksft_raises(NlError) as cm: + cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, + "version": 1, + "tx-key": {'spi': 0x12345678, + 'key': b'\xa5' * 32}, + "sock-fd": s.fileno()}) + ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL) + finally: + _close_psp_conn(cfg, s) + + +def _get_psp_ver_variants(): + for ver in range(4): + yield KsftNamedVariant(f"v{ver}", ver) + + +@ksft_variants(_get_psp_ver_variants()) +def rekey_rx_basic(cfg, version): + """Test rekeying the Rx key of an established connection""" + _init_psp_dev(cfg) + + s = _establish_psp_conn(cfg, version) + try: + data_len = _psp_txrx(cfg, s, 10) + cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) + _rekey_rx(cfg, s, version, data_len) + finally: + _close_psp_conn(cfg, s) + + +@ksft_variants(_get_psp_ver_variants()) +def rekey_tx_basic(cfg, version): + """Test rekeying the Tx key of an established connection""" + _init_psp_dev(cfg) + + s = _establish_psp_conn(cfg, version) + try: + data_len = _psp_txrx(cfg, s, 10) + _remote_key_rotate(cfg) + _rekey_tx(cfg, s, data_len) + finally: + _close_psp_conn(cfg, s) + + +@ksft_variants(_get_psp_ver_variants()) +def rekey_both_sides(cfg, version): + """Test rekeying both directions""" + _init_psp_dev(cfg) + + s = _establish_psp_conn(cfg, version) + try: + data_len = _psp_txrx(cfg, s, 10) + + cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) + _remote_key_rotate(cfg) + + # first, rx then tx + data_len = _rekey_rx(cfg, s, version, data_len) + data_len = _rekey_tx(cfg, s, data_len) + + cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) + _remote_key_rotate(cfg) + + # now, tx then rx + data_len = _rekey_tx(cfg, s, data_len) + _rekey_rx(cfg, s, version, data_len) + finally: + _close_psp_conn(cfg, s) + + def __bad_xfer_do(cfg, s, tx, version='hdr0-aes-gcm-128'): # Make sure we accept the ACK for the SPI before we seal with the bad assoc _check_data_outq(s, 0) @@ -1181,7 +1448,8 @@ def main() -> None: cfg.comm_port), timeout=1) - cases = [data_basic_send, data_mss_adjust] + cases = [data_basic_send, data_mss_adjust, + rekey_rx_basic, rekey_tx_basic, rekey_both_sides] if has_cont: cases += [ @@ -1197,7 +1465,8 @@ def main() -> None: ] ksft_run(cases=cases, globs=globals(), - case_pfx={"dev_", "data_", "assoc_", "removal_"}, + case_pfx={"dev_", "data_", "assoc_", "rekey_", + "removal_"}, args=(cfg, )) cfg.comm_sock.send(b"exit\0") diff --git a/tools/testing/selftests/drivers/net/psp_responder.c b/tools/testing/selftests/drivers/net/psp_responder.c index 57425ecb9561..b3e0fe6d6975 100644 --- a/tools/testing/selftests/drivers/net/psp_responder.c +++ b/tools/testing/selftests/drivers/net/psp_responder.c @@ -5,6 +5,7 @@ #include <sys/poll.h> #include <sys/socket.h> #include <sys/time.h> +#include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> @@ -23,6 +24,7 @@ static bool should_quit; struct opts { int port; int ifindex; + int devid; bool verbose; }; @@ -155,6 +157,100 @@ static void send_str(int sock, int value) send(sock, buf, ret + 1, MSG_WAITALL); } +#define PSP_MAX_KEY_LEN 32 + +struct assoc_msg { + __be32 spi; + __u8 version; + __u8 pad[3]; + char key[PSP_MAX_KEY_LEN]; +}; + +static void +handle_rx_assoc(struct ynl_sock *ys, int data_sock, int comm_sock) +{ + struct assoc_msg msg = {}; + __u32 spi; + + if (data_sock < 0) { + fprintf(stderr, "WARN: rx assoc but no data sock\n"); + send_err(comm_sock); + return; + } + + if (rx_assoc(ys, &spi, msg.key, data_sock)) { + fprintf(stderr, "ERROR: rx_assoc() failed\n"); + send_err(comm_sock); + return; + } + + msg.spi = htonl(spi); + msg.version = psp_vers.rx; + send_ack(comm_sock); + send(comm_sock, &msg, sizeof(msg), MSG_WAITALL); +} + +static void +handle_tx_assoc(struct ynl_sock *ys, char *data, int data_sock, int comm_sock) +{ + struct assoc_msg msg; + + if (data_sock < 0) { + fprintf(stderr, "WARN: tx assoc but no data sock\n"); + send_err(comm_sock); + return; + } + + memcpy(&msg, data, sizeof(msg)); + if (tx_assoc(ys, msg.version, ntohl(msg.spi), msg.key, data_sock)) { + fprintf(stderr, "ERROR: tx_assoc() failed!\n"); + send_err(comm_sock); + return; + } + + send_ack(comm_sock); +} + +static int rotate_key(struct ynl_sock *ys, int devid) +{ + struct psp_key_rotate_rsp *rsp; + struct psp_key_rotate_req *req; + + req = psp_key_rotate_req_alloc(); + + psp_key_rotate_req_set_id(req, devid); + + rsp = psp_key_rotate(ys, req); + psp_key_rotate_req_free(req); + + if (!rsp) { + perror("ERROR: failed to rotate key"); + return -1; + } + + psp_key_rotate_rsp_free(rsp); + + return 0; +} + +static void +handle_key_rotate(struct ynl_sock *ys, struct opts *opts, int comm_sock) +{ + if (opts->devid < 0) { + fprintf(stderr, "WARN: key rotate but no PSP device\n"); + send_err(comm_sock); + return; + } + + if (rotate_key(ys, opts->devid)) { + fprintf(stderr, "ERROR: rotate_key() failed\n"); + send_err(comm_sock); + return; + } + + send_ack(comm_sock); +} + static void run_session(struct ynl_sock *ys, struct opts *opts, int server_sock, int comm_sock) @@ -247,6 +343,9 @@ run_session(struct ynl_sock *ys, struct opts *opts, match; \ }) +#define cmd_w_msg(_name, _type) \ + (off >= sizeof(_name) + sizeof(_type) && cmd(_name)) + do { consumed = false; @@ -261,6 +360,16 @@ run_session(struct ynl_sock *ys, struct opts *opts, fprintf(stderr, "WARN: echo but no data sock\n"); send_ack(comm_sock); } + if (cmd("rx assoc")) + handle_rx_assoc(ys, data_sock, + comm_sock); + if (cmd_w_msg("tx assoc", struct assoc_msg)) { + handle_tx_assoc(ys, buf, data_sock, + comm_sock); + __consume(sizeof(struct assoc_msg)); + } + if (cmd("key rotate")) + handle_key_rotate(ys, opts, comm_sock); if (cmd("data close")) { if (data_sock >= 0) { close(data_sock); @@ -291,6 +400,7 @@ run_session(struct ynl_sock *ys, struct opts *opts, } if (cmd("exit")) should_quit = true; +#undef cmd_w_msg #undef cmd if (!consumed) { @@ -486,6 +596,7 @@ int main(int argc, char **argv) } } psp_dev_get_list_free(dev_list); + opts.devid = devid; if (opts.ifindex && devid < 0) fprintf(stderr, -- 2.52.0

