From b21e24813e2b08be428cf95e01881f38e3166009 Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI Date: Thu, 20 Aug 2026 17:03:51 +0900 Subject: [PATCH 1/5] doc: Document CMP PEEK and POKE security model CMP PEEK and POKE intentionally provide memory access and assume trusted management peers. Document this trust boundary, deployment responsibility, consequences of exposing the service, and the distinction between intended capability and implementation defects. Add the security page to the generated documentation navigation and provide a stable target for direct links. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Yasushi SHOJI --- doc/index.md | 1 + doc/security.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 doc/security.md diff --git a/doc/index.md b/doc/index.md index 847c67088..212b5497e 100644 --- a/doc/index.md +++ b/doc/index.md @@ -63,6 +63,7 @@ mtu outflow protocolstack topology +security tunnel hooks ``` diff --git a/doc/security.md b/doc/security.md new file mode 100644 index 000000000..26d89a9bb --- /dev/null +++ b/doc/security.md @@ -0,0 +1,54 @@ +# Security model and deployment assumptions + +libcsp provides networking and management building blocks for embedded systems. +Applications and deployments decide which peers can reach a node, which services +the node handles, and which link or network protections are required. A service +must not be treated as an authentication or authorization boundary unless its +documentation explicitly defines it as one. + +(cmp-peek-and-poke)= +## CMP PEEK and POKE + +CSP Management Protocol (CMP) PEEK and POKE are remote management and debugging +operations. PEEK reads memory from a CSP node, and POKE writes memory on a CSP +node. This memory access is intentional and is powerful by design. + +These operations are intended for deployments in which access to the relevant +CSP management services is already restricted to trusted peers. PEEK and POKE +do not implement authentication, access-control lists, capabilities, or another +authorization boundary. The CMP handler does not decide whether the requesting +peer is permitted to access memory. A peer permitted to issue functional PEEK +or POKE requests is therefore being given memory-access capability by design. + +If an untrusted or compromised peer can reach functional CMP PEEK or POKE +operations, it may be able to read sensitive memory, modify memory, crash the +target, or otherwise compromise the node. Applications must not expose these +operations to untrusted peers and rely on the CMP handler to reject unauthorized +memory access. + +When a CSP network crosses trust boundaries, the deployment must restrict +access using protections appropriate to its architecture. These may include +network isolation, routing restrictions, hardened gateways or firewalls, +authenticated or encrypted links, or other external controls. libcsp does not +require one universal deployment mechanism. + +`csp_service_handler()` handles packets sent to the CMP port and dispatches PEEK +and POKE requests. Consequently, an application that accepts the CMP port and +passes those packets to `csp_service_handler()` exposes the operations to peers +that can reach that service. + +The current implementations differ: + +- Legacy PEEK and POKE are functional by default. +- PEEK v2 and POKE v2 do not access memory by default. They are functional only + when the platform or application provides the required implementation. + +The intentional semantics above are distinct from an accidental implementation +defect. A report that a reachable, functional PEEK or POKE command performs its +documented memory read or write describes the capability itself. Out-of-bounds +accesses, incorrect length validation, use-after-free defects, parser bugs, +memory corruption, or bypasses of security properties that libcsp claims to +provide are separate issues. The existence of an intentionally powerful command +does not make such defects acceptable. Before reporting PEEK or POKE behavior as +a security defect, verify that the finding goes beyond the documented memory +access capability. From 8b6259d233a9678272bdb2c6ae50bd79f9b5f8c5 Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI Date: Thu, 20 Aug 2026 17:04:09 +0900 Subject: [PATCH 2/5] doc: Link CMP API to security model Users consulting the CMP API need to see that PEEK and POKE assume trusted management peers. Link the API reference to the authoritative security and deployment guidance rather than duplicating it. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Yasushi SHOJI --- doc/api/csp_cmp_h.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/api/csp_cmp_h.rst b/doc/api/csp_cmp_h.rst index e93f57747..dc85af953 100644 --- a/doc/api/csp_cmp_h.rst +++ b/doc/api/csp_cmp_h.rst @@ -1,6 +1,10 @@ CSP Management Protocol (CMP) ============================= +CMP PEEK and POKE intentionally provide memory access and assume that the +management service is restricted to trusted peers. See :ref:`cmp-peek-and-poke` +for their security model and deployment requirements. + .. autocmodule:: csp_cmp.h .. contents:: From 097a039701c85e4ceb98e91f380810662e23878a Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI Date: Thu, 20 Aug 2026 17:04:24 +0900 Subject: [PATCH 3/5] examples: Warn about exposed management services The example servers bind all ports and forward service requests to the default handler, which exposes CMP PEEK and POKE. Make clear that these examples demonstrate functionality rather than a hardened deployment and point readers to the security model. No functionality has been changed. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Yasushi SHOJI --- contrib/zephyr/samples/server-client/main.c | 2 +- doc/example.md | 6 ++++++ examples/csp_server.c | 2 +- examples/csp_server_client.c | 2 +- examples/csp_server_client.py | 1 + examples/python_bindings_example_server.py | 3 ++- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/contrib/zephyr/samples/server-client/main.c b/contrib/zephyr/samples/server-client/main.c index 727c86ba2..7f4eb55ca 100644 --- a/contrib/zephyr/samples/server-client/main.c +++ b/contrib/zephyr/samples/server-client/main.c @@ -61,7 +61,7 @@ void server(void) { break; default: - /* Call the default CSP service handler, handle pings, buffer use, etc. */ + /* This example exposes management services; see doc/security.md. */ csp_service_handler(packet); break; } diff --git a/doc/example.md b/doc/example.md index 6eb930502..40f25dbe4 100644 --- a/doc/example.md +++ b/doc/example.md @@ -5,6 +5,12 @@ simple server/client setup, where the client sends a request to the server and receives a reply. The code can be compiled to an executable using `./examples/buildall.py`. +The example servers bind to all CSP ports and pass default service requests to +`csp_service_handler()`. This exposes CMP management operations, including +PEEK and POKE. These examples demonstrate libcsp functionality; they are not +hardened services intended for exposure to an untrusted network. See +{ref}`cmp-peek-and-poke` for the security model and deployment requirements. + The example supports these drivers and interfaces in CSP: - ZMQHUB: `-z ` diff --git a/examples/csp_server.c b/examples/csp_server.c index 4b7503e1e..777a775b7 100644 --- a/examples/csp_server.c +++ b/examples/csp_server.c @@ -73,7 +73,7 @@ static void * server(void * param) { break; default: - /* Call the default CSP service handler, handle pings, buffer use, etc. */ + /* This example exposes management services; see doc/security.md. */ csp_service_handler(packet); break; } diff --git a/examples/csp_server_client.c b/examples/csp_server_client.c index da1bd466c..b429f241d 100644 --- a/examples/csp_server_client.c +++ b/examples/csp_server_client.c @@ -59,7 +59,7 @@ static void * server(void * param) { break; default: - /* Call the default CSP service handler, handle pings, buffer use, etc. */ + /* This example exposes management services; see doc/security.md. */ csp_service_handler(packet); break; } diff --git a/examples/csp_server_client.py b/examples/csp_server_client.py index ad56a85b3..0933ce13e 100644 --- a/examples/csp_server_client.py +++ b/examples/csp_server_client.py @@ -37,6 +37,7 @@ def server_task(addr: int, port: int) -> None: data=csp.packet_get_data(packet).decode('utf-8')) ) else: + # This example exposes management services; see doc/security.md. csp.service_handler(conn, packet) diff --git a/examples/python_bindings_example_server.py b/examples/python_bindings_example_server.py index af35cc4cb..874d1763f 100644 --- a/examples/python_bindings_example_server.py +++ b/examples/python_bindings_example_server.py @@ -89,7 +89,8 @@ def csp_server(): libcsp.sendto_reply(packet, reply, libcsp.CSP_O_NONE) else: - # pass request on to service handler if the given packet is a service-request + # This example exposes management services; see doc/security.md. + # Pass request on to service handler if the given packet is a service-request # (ie: destination port is [0-6] see "include\csp\csp_types.h" line 47-55) # parameters: {connection} {packet} # will handle and send reply packets if necessary From cba1c7ae8ca0330549c296cf32a42d291bd6c37a Mon Sep 17 00:00:00 2001 From: edvard Date: Thu, 20 Aug 2026 14:35:02 +0200 Subject: [PATCH 4/5] rdp: ack only when space for window and the NULL signal At the end of a connection when the final window gets send with the RST the NULL pointer at connection close could get droppped. --- src/csp_rdp.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/csp_rdp.c b/src/csp_rdp.c index 4eaf4ea84..9e5099f25 100644 --- a/src/csp_rdp.c +++ b/src/csp_rdp.c @@ -305,7 +305,7 @@ static inline bool csp_rdp_should_ack(csp_conn_t * conn) { int csp_rdp_check_ack(csp_conn_t * conn) { /* Check RX queue for spare capacity */ - if ((unsigned int) abs(CSP_CONN_RXQUEUE_LEN - csp_queue_size(conn->rx_queue)) < conn->rdp.window_size) { + if ((uint32_t)csp_queue_free(conn->rx_queue) <= conn->rdp.window_size) { return CSP_ERR_NONE; } @@ -673,7 +673,7 @@ bool csp_rdp_new_packet(csp_conn_t * conn, csp_packet_t * packet) { if (packet->length <= sizeof(rdp_header_t)) goto discard_open; - /* If message is not in sequence, send EACK and store packet */ + /* If message is not in sequence, store packet for next flush */ if (rx_header->seq_nr != (uint16_t)(conn->rdp.rcv_cur + 1)) { if (csp_rdp_rx_queue_add(conn, packet, rx_header->seq_nr) != CSP_ERR_NONE) { csp_rdp_check_ack(conn); @@ -737,7 +737,9 @@ bool csp_rdp_new_packet(csp_conn_t * conn, csp_packet_t * packet) { * by sending a NULL pointer, user-space must close connection */ if (conn->dest_socket == NULL) { csp_conn_close(conn, closed_by); - csp_conn_enqueue_packet(conn, NULL); + if (csp_conn_enqueue_packet(conn, NULL) != CSP_ERR_NONE) { + csp_rdp_error("RDP %p: Could not signal close to userspace, RX queue full\n", (void *)conn); + } } else { /* New connection, userspace doesn't know anything about it yet - so it can be completely closed */ csp_conn_close(conn, closed_by | CSP_RDP_CLOSED_BY_USERSPACE); From 2d226f82590de15be239bbd025cc9488088004ef Mon Sep 17 00:00:00 2001 From: edvard Date: Thu, 20 Aug 2026 14:43:22 +0200 Subject: [PATCH 5/5] rdp: csp_rdp_queue_get reenqueue packets without modifying conn A csp_rdp_queue_get from one connection would apply its own connection when enqueuing packets from other connections. --- src/csp_rdp_queue.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/csp_rdp_queue.c b/src/csp_rdp_queue.c index 08567c1ec..70af86738 100644 --- a/src/csp_rdp_queue.c +++ b/src/csp_rdp_queue.c @@ -34,13 +34,6 @@ static int __csp_rdp_queue_flush(csp_queue_handle_t queue, csp_conn_t * conn) { return ret; } -static void csp_rdp_queue_add(csp_queue_handle_t queue, csp_conn_t * conn, csp_packet_t * packet) { - packet->conn = conn; - if (csp_queue_enqueue(queue, &packet, 0) != CSP_QUEUE_OK) { - csp_buffer_free(packet); - } -} - static csp_packet_t * csp_rdp_queue_get(csp_queue_handle_t queue, csp_conn_t * conn) { csp_packet_t * packet; int size = csp_queue_size(queue); @@ -57,7 +50,9 @@ static csp_packet_t * csp_rdp_queue_get(csp_queue_handle_t queue, csp_conn_t * c } /* Put it back and check next */ - csp_rdp_queue_add(queue, conn, packet); + if (csp_queue_enqueue(queue, &packet, 0) != CSP_QUEUE_OK) { + csp_buffer_free(packet); + } } return NULL; @@ -97,7 +92,10 @@ int csp_rdp_queue_tx_size(void) { } void csp_rdp_queue_tx_add(csp_conn_t * conn, csp_packet_t * packet) { - csp_rdp_queue_add(tx_queue, conn, packet); + packet->conn = conn; + if (csp_queue_enqueue(tx_queue, &packet, 0) != CSP_QUEUE_OK) { + csp_buffer_free(packet); + } } csp_packet_t * csp_rdp_queue_tx_get(csp_conn_t * conn) { @@ -109,7 +107,10 @@ int csp_rdp_queue_rx_size(void) { } void csp_rdp_queue_rx_add(csp_conn_t * conn, csp_packet_t * packet) { - csp_rdp_queue_add(rx_queue, conn, packet); + packet->conn = conn; + if (csp_queue_enqueue(rx_queue, &packet, 0) != CSP_QUEUE_OK) { + csp_buffer_free(packet); + } } csp_packet_t * csp_rdp_queue_rx_get(csp_conn_t * conn) {