3439 Commits
Author SHA1 Message Date
Jon Shallow 113472a4b4 Cache: Include the option length in the hash calculations
This is to prevent potential cache-key collisions.
2026-10-22 14:35:26 +01:00
Scorpion ced2013038 Block: Fix off-by-one when removing a received block range
blocks_delete_entry() moved used - i entries instead of used - i - 1
when deleting a single-block range that is not the last, reading one
coap_lg_range past the live set. Match the sibling branch below it.
2026-10-22 13:06:45 +01:00
Scorpion 51a156f7cb OSCORE: Don't read the option value before the length check
oscore_decode_option_value() read opt_value[0] before the early return
for a zero-length (empty) option, so an empty OSCORE option dereferenced
a byte the option does not own. Move the read after the length check.
2026-10-22 13:06:45 +01:00
Scorpion dc2f20b55b OSCORE: Reject ciphertext too short for the AEAD tag
coap_oscore_decrypt_pdu() only checked encrypt_len == 0 before computing
encrypt_len - tag_len (size_t), so a protected payload shorter than the
tag underflowed to a huge value used for plain_pdu->used_size and the
AEAD output buffer size. This is reached before the payload is
authenticated.

Reject a payload that cannot hold the tag plus the inner Code byte up
front and handle it like any other decryption failure: a request is
answered with 4.00 "Decryption failed" and the replay state is rolled
back, so an unauthenticated short request cannot advance the replay
window. Also guard the subtraction in cose_encrypt0_decrypt().

Add a regression test for every payload length from one byte through
the tag length.
2026-10-22 13:06:45 +01:00
usama khan 537d06f593 OSCORE: Roll back replay window on decryption failure
oscore_validate_sender_seq() moves the replay window on before the
message has been decrypted, and oscore_roll_back_seq() is meant to undo
that when decryption fails. It only restored last_seq and
sliding_window when the saved values were non zero, and never restored
initial_state. For a recipient context still in its initial state
(rfc8613_b_1_2 false), or one whose last_seq is 0, a single request
with a forged Partial IV and an invalid tag left last_seq at the
sender's chosen value, so the genuine peer's following requests were
rejected as outside the replay window.

The client did not roll back at all when a response failed decryption,
and the server called oscore_update_seq_num_cb() with the new window
before decrypting the request, so credential storage could be updated
with a forged sequence number even when the in memory window was
restored.

Record whether there is a window update to undo and restore
initial_state with it, roll back on response decryption failure as
well, and only call oscore_update_seq_num_cb() once the request has
been decrypted.

Add a unit test.
2026-10-22 12:56:02 +01:00
Alejandro Pacheco 7a7336aa89 coap_dtls_receive(): Stop using the TLS data after coap_handle_dgram()
Handling a received PDU can disconnect the session, for example when
the response cannot be sent and coap_send_internal() calls
coap_session_disconnected_lkd(), which closes session->tls and frees
it. After coap_handle_dgram() returned, coap_dtls_receive() in the
wolfSSL backend still cleared the pdu pointer in the freed TLS data,
and the OpenSSL backend read it through the already freed BIO.

Clear the pdu pointer before passing the decrypted data to
coap_handle_dgram() and return its result directly, as the GnuTLS
backend already does, so the TLS data is never touched once the PDU
has been handed on. Do the same before the DTLS connected event and
l_establish() on handshake completion. Mbed TLS checked session->tls
for NULL instead; give it the same structure.

Add a unit test where the server's response write fails.
2026-10-17 13:33:06 +01:00
Alejandro Pacheco da858655d6 coap_resource.c: Do not hold the next subscriber across a notification
coap_notify_observers() walked the subscribers with LL_FOREACH_SAFE(),
which holds on to the next subscription while the current one is
notified. The request handler runs with the lock released, and a
notification that cannot be sent disconnects the session, which deletes
all of that session's subscriptions. When the next subscription was one
of those, the walk carried on with freed memory.

Mark the subscriptions to visit at the start of the walk and look up the
next marked one from the head of the list after each notification, so
nothing deleted during a notification is used afterwards.

Subscriptions added during the walk are not notified in that pass, as
before.

Add a unit test.
2026-10-17 12:43:11 +01:00
Alejandro Pacheco a8f89ced78 coap_mbedtls.c: Zero terminate the EC JPAKE cipher suite list
set_ciphersuites() builds the PSK, PKI and EC JPAKE cipher suite lists
in two passes, one counting and one filling, each with its own copy of
the selection rules, and then zero terminated the PSK and PKI lists
only. The EC JPAKE list is handed to mbedtls_ssl_conf_ciphersuites(),
which reads it up to the 0 entry, so an EC JPAKE client took whatever
was in the last slot as the next cipher suite and could read past the
end of the allocation when writing its ClientHello.

Decide which lists a cipher suite goes in with one function used by
both passes, and zero terminate every list, so the counts, contents and
terminators of the lists can no longer disagree.

Add a unit test.
2026-10-17 12:30:39 +01:00
Jon Shallow 49f8115c33 CMakeLists.txt: Fix epoll support check 2026-10-17 10:29:38 +01:00
Alejandro Pacheco 1cddf3f3b0 coap_mbedtls.c: Do not move cached PKI SNI entries
pki_sni_callback() kept the PKI SNI cache as one array of entries that
hold the CA chain, certificate and key by value, and grew it with
realloc() for each new SNI. The certificate and key of an entry are
handed to the handshake with mbedtls_ssl_set_hs_ca_chain() and
mbedtls_ssl_set_hs_own_cert(), so when a second client asked for a new
SNI while another handshake was still running, that handshake was left
using freed memory.

Allocate each entry on its own and keep an array of pointers to them,
so an entry stays where it is for the life of the context. Also free
the partly set up credentials when setting up a new entry fails.

Add a unit test.
2026-10-16 15:02:07 +01:00
Alejandro Pacheco cfa1cf3d17 coap_block.c: Body length of a reassembled body is where the last block ends
When a block-wise body is passed on as a single body, its length was taken
from total_len, which tracks the peer's Size2 / Size1 rather than the data
actually received.

Client Q-Block2: total_len was raised to a larger Size2 in a later block
before the Size2 mismatch check, so the check never fired for that case,
and nothing tied the last block to Size2. The body handed to the
application could then be longer than the data buffer behind it, or
include its uninitialised tail.

Server Block1 / Q-Block1: total_len kept a Size1 that was larger than
where the last block ended, so the body included the uninitialised tail
of the buffer allocated for the Size1 estimate.

Now the last block (M bit not set) defines the body length: for Q-Block2
and Q-Block1 it must end at Size2 / Size1 (RFC9177 requires these to be the
full size), and for Block1 / Block2 it replaces the Size2 / Size1 estimate
(RFC7959 4). A new total_len_set flag records that the last block has set
total_len, after which a block that ends past it, or another last block
that ends elsewhere, is rejected instead of changing the length.

Add unit tests to tests/test_block.c.
2026-10-16 15:01:00 +01:00
Alejandro Pacheco 92fc71e908 coap_block.c: Use get_func wherever a request body is added
For a request set up with coap_add_data_large_request_app(), the data
is provided by the application's get_func and there is no data pointer.
Three places still took the body from that NULL pointer, so
coap_add_data() was passed a NULL pointer:

- check_freshness(), re-sending the next block with the Echo option
  from a 4.01 response.
- coap_cancel_observe_lkd(), re-adding the observe request's body to
  the cancel request.
- coap_add_data_large_internal(), when the application sets a Block1
  option with a non-zero block number to send a single block.

Use get_func in all of these, as the other places that send a block of
an lg_xmit already do. The single block and the whole-body cases now
share add_get_func_data(), which also checks the allocation and no
longer leaks the buffer when get_func fails.

Add unit tests.
2026-10-16 14:14:20 +01:00
Alejandro Pacheco 85ed6ebb93 coap_proxy.c: Check every proxy entry in coap_proxy_remove_association()
With track_client_session set, an incoming session gets its own proxy
entry for each upstream server it uses. coap_proxy_remove_association()
returned after the first entry tracking the session, so the proxy
requests of that session held in later entries were left in place when
the session was deleted, and their upstream sessions were not released.
A later upstream response was then forwarded to the freed session.

Carry on with the remaining entries instead of returning.

Add a unit test.
2026-10-16 10:52:31 +01:00
Jon Shallow 7c89af0d7d coap_pdu.c: Length of zero is valid for Option Query 2026-10-16 10:36:00 +01:00
Jon Shallow 892aa2036f Ubuntu 26.04.1: Fix scan-build warnings
Getting ready for CI builds using Ubuntu 26.04.
2026-10-14 10:49:16 +01:00
Jon Shallow c3b6709029 RIOT: Fix example server build against latest RIOT code 2026-10-14 10:29:01 +01:00
Mario KichererandCopilot 3dc2c812c6 Drop empty asynchronous observer responses
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-06 14:14:41 +01:00
Jon Shallow cd9f19055b CI: Add in sanitizer runs for 32 and 64 bits 2026-10-05 10:30:56 +01:00
Jon Shallow b2ba5ff454 OpenSSL: Fix trust store for Ubuntu 26.04 (OpenSSL 3.5.0e-dev) 2026-10-05 10:12:56 +01:00
Jon Shallow 75a532c1f7 Ubuntu 26.04.1: Fix new compiler warnings
Update pre-commit versions as well.
2026-10-02 12:38:54 +01:00
Magdalena Szumny c63c8f7cb7 Make max latency configurable 2026-09-29 13:35:41 +01:00
Jon Shallow bbeb8c1d6a coap_prng: Support use of getentropy() if getrandom() is not available 2026-09-28 18:34:07 +01:00
Jon Shallow dad3028d6f Zephyr: Make it easier to change Zephyr base branch 2026-09-28 17:19:51 +01:00
Jon Shallow 15bba8e9f7 coap_block.c: Limit the number of lg_srcv tracking (Q-)Block1 input
Reduce value of partial timeout if there are a lot of outstanding
large block receipts that are not active.

Add function coap_context_set_max_bodies_ram() to define an upper memory
limit usage across all active body receipts.
2026-09-28 15:24:18 +01:00
Jon Shallow 79e52246d6 Contiki-NG: Update to release/v5.2 2026-09-23 15:04:23 +01:00
Jon Shallow 2f55206fee src/coap_block.c: Handle missing Size1 option
Handle when Size1 is smaller than actual indicated size.
2026-09-23 09:36:03 +01:00
Jon Shallow 2b1ec9f270 coap-client: Always define a token when doing a subscribe
Some servers insist there being a token when a subscription
Observe is initiated.
2026-09-22 20:27:57 +01:00
Jon Shallow 0583a6734a coap_block.c: Do not return timeout value of 0 for lg_xmit timeouts
This can cause a process to wait forever before retransmitting if
there is no input.
2026-09-22 17:07:43 +01:00
Jon Shallow 5ce12ab656 coap_block.c: Remove unnecessary uri_path memory allocation 2026-09-21 16:15:15 +01:00
Jon Shallow dd0c1e8c2c 32 bit build: Support building 32 bit executables
On a linux system

./configure --host=i386

Currently TLS support is not available.

Fix a 32 bit CBOR issue.
2026-09-21 11:14:01 +01:00
Guancheng Wang 19bc71e620 cmake: build/install examples only when the library mode they require is enabled
Examples are only built when the library mode they depend on is enabled,
otherwise their link step fails with undefined references. The tiny example
is gated on client mode only.

Signed-off-by: warter666 <271496918+warter666@users.noreply.github.com>
2026-09-20 15:47:27 +01:00
Guancheng Wang 53e1bcaeba proxy: guard coap_proxy_get_add_list_entry() against two NULL sessions
`coap_proxy_get_add_list_entry(incoming, ongoing, ...)` dereferences `ongoing->context` when `incoming == NULL`. Current in-tree callers always pass exactly one non-NULL session, but the function is a public-ish internal helper and a future caller passing both NULL would dereference NULL immediately. cppcheck flags exactly this (`nullPointer` on `ongoing`).

This adds an explicit early `return NULL` when both arguments are NULL - defensive only, no behavior change for existing callers.

Signed-off-by: warter666 <121139223+Warterkk@users.noreply.github.com>
2026-09-19 12:25:46 +02:00
Guancheng Wang f6b20b0d3d debug: fix printf format mismatches reported by -Wextra
Three format-string/argument type mismatches in `src/coap_debug.c`, all reported by `-Wall -Wextra` (fatal under -Werror):

1. `msg_code_string()`: `"%u.%02u"` receives `int` expressions (uint16_t promotes to int) - cast both to `unsigned`.
2. `print_content_format()`: `"%d"` receives `unsigned int format_type` - use `%u`.
3. block-size printing: `"%u"` receives the `int` expression `1 << (...)` - make the shift and cast unsigned.

No behavior change on any realistic input; only the int-vs-unsigned variance is fixed.

Signed-off-by: warter666 <121139223+Warterkk@users.noreply.github.com>
2026-09-19 12:23:40 +02:00
Guancheng Wang 6cb2addb1e block: compile full_match() only when client support is enabled
With `-DENABLE_CLIENT_MODE=OFF` (i.e. `COAP_CLIENT_SUPPORT=0`), `full_match()` in `src/coap_block.c` is defined but its only caller is compiled out, producing `-Wunused-function` (fatal in -Werror builds):

```
src/coap_block.c:479:1: error: unused function 'full_match' [-Werror=-Wunused-function]
```

Wrap the static-inline definition in the same `#if COAP_CLIENT_SUPPORT` guard as its caller.

Signed-off-by: warter666 <121139223+Warterkk@users.noreply.github.com>
2026-09-19 12:19:59 +02:00
Guancheng Wang 899a9e533a ws: fix NULL dereference on a malformed client-handshake status line
In `coap_ws_rd_http_header_client()` the first header (the HTTP status line) is split by `coap_ws_split_rd_header()`, which returns NULL when the buffered line contains no space/tab separator. The second call site checks for NULL, but the first one does not:

```c
value = coap_ws_split_rd_header(session);

if (strcmp((char *)ws->http_hdr, "HTTP/1.1") != 0 ||
    atoi(value) != 101) {
```

If the HTTP header buffer is exactly `"HTTP/1.1"` (no trailing space), `strcmp()` returns 0, the `||` short-circuit does not apply, and `atoi(NULL)` is called - a remote crash in the WS client handshake when a malicious or broken server sends a truncated status line.

Fix: return 0 when `value` is NULL, mirroring the second call site.

The library builds cleanly after the change (cmake -DENABLE_DTLS=OFF, gcc/clang, 0 warnings).

Signed-off-by: warter666 <121139223+Warterkk@users.noreply.github.com>
2026-09-19 11:09:15 +02:00
Magdalena Szumny 97b8a1edd0 OSCORE: preserve recipient reference on noop attachment 2026-09-18 12:00:51 +02:00
Jon Shallow 93694e6c24 RIOT: Add in TCP support
Need to make use of LwIP interface for TCP.
2026-09-16 15:03:45 +02:00
Denis Begun 274767a29c Mbed TLS: Return the total bytes written over TLS
When a message spans multiple mbedTLS records, coap_tls_write() returns
only the final mbedtls_ssl_write() result despite sending the entire
message. Its caller treats the PDU as partially written and queues an
already transmitted suffix.

Update the successful return value to the accumulated byte count.
Preserve the existing nonblocking retry and fatal-error handling.

Validated with native mbedTLS 3.6.2 loopback transfers and a focused
ASan/UBSan harness covering record boundaries and WANT_READ/WANT_WRITE.
2026-09-16 10:21:30 +02:00
Jon Shallow 18df85af6d Nits: Fix documentation 2026-09-14 09:52:17 +02:00
Jon Shallow 9c5983b3f2 Responses: RST sent only for CON or NON
Also RST response packets that have no response handling context.
2026-09-14 09:30:18 +02:00
Jon Shallow 68849057f1 Delayqueue: Add delayqueue_tail to coap_session to avoid scanning long queues 2026-09-11 12:30:08 +01:00
Mario Kicherer b20841965e Fix callback data handling for TCP BERT
TCP BERT payloads can exceed the 1024-byte callback scratch buffer. Allocate a temporary buffer sized to the requested chunk, pass it to the large-data callback, and release it after the PDU has copied the data. Preserve the stack buffer for regular block transfers and report allocation failures explicitly.
2026-09-10 17:13:20 +01:00
Jon Shallow fab32682ea coap_pdu_duplicate: Make max size of duplicated PDU same as original
When calling coap_pdu_check_resize(), start with a smaller size for embedded
systems.
2026-09-10 14:59:45 +01:00
Jon Shallow 64211f744e Block1: Handle concurrent transfers over the same session 2026-09-09 14:12:28 +01:00
Jon Shallow 9a27dbd0be OSCORE: Delete association on error response 2026-09-09 14:09:35 +01:00
Magdalena Szumny b61cb7683a Use cryptographic safe entropy for Zephyr 2026-09-08 19:42:10 +01:00
Michiel De Witte e61c8c8ef7 Preserve the observe request's local address for notifications
Packets from the same peer can overwrite a session's local address with a
multicast destination. Save the address when an observation is created or
renewed and restore it before generating notifications, so deferred responses
snapshot the correct address instead of being dropped as invalid multicast.

Add regression coverage for CON and NON observations, including renewals and
the local address captured by a notification's async response.

Validation: 215 unit tests passed (1712 assertions).
2026-09-07 15:58:29 +01:00
Jon Shallow 421107b4b7 Resource: Caution about setting up resources in thread-safe manner 2026-09-07 15:53:01 +01:00
Jon Shallow 73bd9fa543 OSCORE: Remove unnecessary CBOR assert()
Report error when creating AAD or External AAD when CBOR fails.
2026-09-07 15:27:52 +01:00
Michiel De WitteandClaude Opus 5 5e86d78e6d coap_net.c: Do not send an Empty message as a separate response
A request handler that leaves the response code unset asks for no
response to be sent.  That is honoured for a piggybacked ACK (the token
gets stripped, so an empty ACK goes out) and for a non-confirmable or
reliable response (dropped in no_response()), but not for a separate
response set up with coap_register_async().

There the response is turned into a CON, and a 0.00 CON was put on the
wire.  An Empty message is a ping (RFC 7252 4.2), not a response, and the
PDU still carries the request's token, which RFC 7252 4.1 does not allow.
Being confirmable, it was then retransmitted up to MAX_RETRANSMIT times,
usually at a client port that had long gone away.

Drop it like the other two cases, delete it without logging it as a
dropped response since the application asked for this, and document in
coap_handler(3) that a separate response is not sent either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 15:22:26 +01:00