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/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:: 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/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. 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 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); 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) {