Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sys/net/nanocoap: fix buffer overflow in separate response handling #21075

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cpu/avr8_common/avr_libc_extra/include/unistd.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ int pipe(int [2]);
ssize_t pread(int, void *, size_t, off_t);
ssize_t pwrite(int, const void *, size_t, off_t);
ssize_t read(int, void *, size_t);
ssize_t readlink(const char *restrict, char *restrict, size_t);
ssize_t readlinkat(int, const char *restrict, char *restrict, size_t);
ssize_t readlink(const char *__restrict, char *__restrict, size_t);
ssize_t readlinkat(int, const char *__restrict, char *__restrict, size_t);
int rmdir(const char *);
int setegid(gid_t);
int seteuid(uid_t);
Expand Down
8 changes: 6 additions & 2 deletions examples/nanocoap_server/coap_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@
(uint8_t *)sub_uri, sub_uri_len);
}

static ssize_t _riot_board_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 44 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
(void)context;
return coap_reply_simple(pkt, COAP_CODE_205, buf, len,
COAP_FORMAT_TEXT, (uint8_t*)RIOT_BOARD, strlen(RIOT_BOARD));
}

static ssize_t _riot_block2_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 51 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
(void)context;
coap_block_slicer_t slicer;
Expand All @@ -63,7 +63,7 @@

/* Add actual content */
bufpos += coap_blockwise_put_bytes(&slicer, bufpos, block2_intro, sizeof(block2_intro)-1);
bufpos += coap_blockwise_put_bytes(&slicer, bufpos, (uint8_t*)RIOT_VERSION, strlen(RIOT_VERSION));

Check warning on line 66 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
bufpos += coap_blockwise_put_char(&slicer, bufpos, ')');
bufpos += coap_blockwise_put_bytes(&slicer, bufpos, block2_board, sizeof(block2_board)-1);
bufpos += coap_blockwise_put_bytes(&slicer, bufpos, (uint8_t*)RIOT_BOARD, strlen(RIOT_BOARD));
Expand All @@ -81,7 +81,7 @@
buf, len, payload_len, &slicer);
}

static ssize_t _riot_value_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 84 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
(void) context;

Expand All @@ -92,7 +92,7 @@
/* read coap method type in packet */
unsigned method_flag = coap_method2flag(coap_get_code_detail(pkt));

switch(method_flag) {

Check warning on line 95 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

keyword 'switch' not followed by a single space
case COAP_GET:
/* write the response buffer with the internal value */
p += fmt_u32_dec(rsp, internal_value);
Expand Down Expand Up @@ -177,7 +177,7 @@
.path = "/riot/board", .methods = COAP_GET, .handler = _riot_board_handler
};
NANOCOAP_RESOURCE(value) {
.path = "/riot/value", .methods = COAP_GET | COAP_PUT | COAP_POST, .handler = _riot_value_handler

Check warning on line 180 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
};
NANOCOAP_RESOURCE(ver) {
.path = "/riot/ver", .methods = COAP_GET, .handler = _riot_block2_handler
Expand All @@ -199,19 +199,23 @@
response, sizeof(response));
}

static ssize_t _separate_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 202 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
static event_timeout_t event_timeout;
static event_callback_t event_timed = EVENT_CALLBACK_INIT(_send_response, &_separate_ctx);

if (event_timeout_is_pending(&event_timeout) && !sock_udp_ep_equal(context->remote, &_separate_ctx.remote)) {

Check warning on line 207 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
puts("_separate_handler(): response already scheduled");
return coap_build_reply(pkt, COAP_CODE_SERVICE_UNAVAILABLE, buf, len, 0);
}

puts("_separate_handler(): send ACK, schedule response");
if (nanocoap_server_prepare_separate(&_separate_ctx, pkt, context)) {
puts("_separate_handler(): failed to prepare context for separate response");
/* send a reset message, as we don't support large tokens here */
return coap_build_reply(pkt, 0, buf, len, 0);
}

nanocoap_server_prepare_separate(&_separate_ctx, pkt, context);
puts("_separate_handler(): send ACK, schedule response");

event_timeout_ztimer_init(&event_timeout, ZTIMER_MSEC, EVENT_PRIO_MEDIUM,
&event_timed.super);
Expand Down
59 changes: 57 additions & 2 deletions sys/fmt/fmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@
*/

#include <assert.h>
#include <stdarg.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "kernel_defines.h"
#include "container.h"
#include "fmt.h"
#include "modules.h"

extern ssize_t stdio_write(const void* buffer, size_t len);

Expand Down Expand Up @@ -512,6 +514,59 @@ uint32_t scn_u32_hex(const char *str, size_t n)
return res;
}

static bool _get_nibble(uint8_t *dest, char _c)
{
uint8_t c = _c;
if (((uint8_t)'0' <= c) && (c <= (uint8_t)'9')) {
*dest = c - (uint8_t)'0';
return true;
}

if (((uint8_t)'a' <= c) && (c <= (uint8_t)'f')) {
*dest = c - (uint8_t)'a' + 10;
return true;
}

if (((uint8_t)'A' <= c) && (c <= (uint8_t)'F')) {
*dest = c - (uint8_t)'A' + 10;
return true;
}

return false;
}

ssize_t scn_buf_hex(void *_dest, size_t dest_len, const char *hex, size_t hex_len)
{
uint8_t *dest = _dest;
assert((dest != NULL) || (dest_len == 0));
assert((hex != NULL) || (hex_len == 0));

if (hex_len & 1) {
/* we need to chars per every byte, so odd inputs don't work */
return -EINVAL;
}

size_t len = hex_len >> 1;
if (len > dest_len) {
return -EOVERFLOW;
}

for (size_t pos = 0; pos < len; pos++) {
uint8_t high, low;
if (!_get_nibble(&high, hex[pos << 1])) {
return -EINVAL;
}

if (!_get_nibble(&low, hex[(pos << 1) + 1])) {
return -EINVAL;
}

dest[pos] = (high << 4) | low;
}

return len;
}

/* native gets special treatment as native's stdio code is ... special.
* And when not building for RIOT, there's no `stdio_write()`.
* In those cases, just defer to `printf()`.
Expand Down
30 changes: 29 additions & 1 deletion sys/include/fmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@
#ifndef FMT_H
#define FMT_H

#include <stdint.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -427,6 +428,33 @@ uint32_t scn_u32_dec(const char *str, size_t n);
*/
uint32_t scn_u32_hex(const char *str, size_t n);

/**
* @brief Convert a hex to binary
*
* @param[out] dest Destination buffer to write to
* @param[in] dest_len Size of @p dest in bytes
* @param[in] hex Hex string to convert
* @param[in] hex_len Length of @p hex in bytes
*
* @return Number of bytes written
* @retval -EINVAL @p hex_len is odd or @p hex contains non-hex chars
* @retval -EOVERFLOW Destination to small
*
* @pre If @p dest_len is > 0, @p dest is not a null pointer
* @pre If @p hex_len is > 0, @p hex is not a null pointer
*
* Examples
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
* const char *hex = "deadbeef";
* uint8_t binary[sizeof(hex) / 2];
* ssize_t len = scn_buf_hex(binary, sizeof(binary), hex, strlen(hex));
* if (len >= 0) {
* make_use_of(binary, len);
* }
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
ssize_t scn_buf_hex(void *dest, size_t dest_len, const char *hex, size_t hex_len);

/**
* @brief Print string to stdout
*
Expand Down
10 changes: 8 additions & 2 deletions sys/include/net/nanocoap_sock.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,15 @@ typedef struct {
* @param[out] ctx Context information for separate response
* @param[in] pkt CoAP packet to which the response will be generated
* @param[in] req Context of the CoAP request
*
* @retval 0 Success
* @retval -EOVERFLOW Storing context would have overflown buffers in @p ctx
* (e.g. RFC 8974 (module `nanocoap_token_ext`) is in
* use and token too long)
* @retval <0 Other error
*/
void nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx,
coap_pkt_t *pkt, const coap_request_ctx_t *req);
int nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx,
coap_pkt_t *pkt, const coap_request_ctx_t *req);

/**
* @brief Send a separate response to a CoAP request
Expand Down
22 changes: 11 additions & 11 deletions sys/net/application_layer/nanocoap/nanocoap.c
Original file line number Diff line number Diff line change
Expand Up @@ -656,23 +656,23 @@
uint8_t *rbuf, unsigned rlen, unsigned payload_len)
{
unsigned tkl = coap_get_token_len(pkt);
unsigned type = COAP_TYPE_NON;

if (!code) {
/* if code is COAP_CODE_EMPTY (zero), assume Reset (RST) type.
* RST message have no token */
type = COAP_TYPE_RST;
tkl = 0;
}
else if (coap_get_type(pkt) == COAP_TYPE_CON) {
type = COAP_TYPE_ACK;
}
unsigned len = sizeof(coap_hdr_t) + tkl;

if ((len + payload_len) > rlen) {
return -ENOSPC;
}

/* if code is COAP_CODE_EMPTY (zero), assume Reset (RST) type */
unsigned type = COAP_TYPE_RST;
if (code) {
if (coap_get_type(pkt) == COAP_TYPE_CON) {
type = COAP_TYPE_ACK;
}
else {
type = COAP_TYPE_NON;
}
}

uint32_t no_response;
if (coap_opt_get_uint(pkt, COAP_OPT_NO_RESPONSE, &no_response) == 0) {

Expand Down Expand Up @@ -908,7 +908,7 @@
return offset;
}

size_t coap_put_option(uint8_t *buf, uint16_t lastonum, uint16_t onum, const void *odata, size_t olen)

Check warning on line 911 in sys/net/application_layer/nanocoap/nanocoap.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
assert(lastonum <= onum);

Expand Down
21 changes: 17 additions & 4 deletions sys/net/application_layer/nanocoap/sock.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@

/* random timeout, deadline for receive retries */
uint32_t timeout = random_uint32_range((uint32_t)CONFIG_COAP_ACK_TIMEOUT_MS * US_PER_MS,
(uint32_t)CONFIG_COAP_ACK_TIMEOUT_MS * CONFIG_COAP_RANDOM_FACTOR_1000);

Check warning on line 210 in sys/net/application_layer/nanocoap/sock.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
uint32_t deadline = _deadline_from_interval(timeout);

/* check if we expect a reply */
Expand Down Expand Up @@ -1075,11 +1075,22 @@
nanocoap_server_start(&local);
}

void nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx,
coap_pkt_t *pkt, const coap_request_ctx_t *req)
int nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx,
coap_pkt_t *pkt, const coap_request_ctx_t *req)
{
ctx->tkl = coap_get_token_len(pkt);
memcpy(ctx->token, coap_get_token(pkt), ctx->tkl);
size_t tkl = coap_get_token_len(pkt);
if (tkl > sizeof(ctx->token)) {
DEBUG_PUTS("nanocoap: token too long for separate response ctx");
/* Legacy code may not check the return value. To still have somewhat
* sane behavior, we ask for no response for any response class.
* Getting no reply is certainly not ideal, but better than one without
* a matching token. */
memset(ctx, 0, sizeof(*ctx));
ctx->no_response = 0xff;
return -EOVERFLOW;
}
ctx->tkl = tkl;
memcpy(ctx->token, coap_get_token(pkt), tkl);
memcpy(&ctx->remote, req->remote, sizeof(ctx->remote));
#ifdef MODULE_SOCK_AUX_LOCAL
assert(req->local);
Expand All @@ -1088,6 +1099,8 @@
uint32_t no_response = 0;
coap_opt_get_uint(pkt, COAP_OPT_NO_RESPONSE, &no_response);
ctx->no_response = no_response;

return 0;
}

int nanocoap_server_send_separate(const nanocoap_server_response_ctx_t *ctx,
Expand Down
1 change: 1 addition & 0 deletions tests/net/nanocoap_cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ USEMODULE += gnrc_ipv6_default

USEMODULE += nanocoap_sock
USEMODULE += nanocoap_resources
USEMODULE += fmt # scn_buf_hex() for shell cmd client_token

# boards where basic nanocoap functionality fits, but no VFS
LOW_MEMORY_BOARDS := \
Expand Down
39 changes: 36 additions & 3 deletions tests/net/nanocoap_cli/nanocli_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <stdlib.h>
#include <string.h>

#include "fmt.h"
#include "net/coap.h"
#include "net/gnrc/netif.h"
#include "net/nanocoap.h"
Expand Down Expand Up @@ -80,6 +81,14 @@ static ssize_t _send(coap_pkt_t *pkt, size_t len,
return nanocoap_request(pkt, NULL, &remote, len);
}

#if MODULE_NANOCOAP_TOKEN_EXT
# define CLIENT_TOKEN_LENGTH_MAX 16
#else
# define CLIENT_TOKEN_LENGTH_MAX COAP_TOKEN_LENGTH_MAX
#endif
static uint8_t _client_token[CLIENT_TOKEN_LENGTH_MAX] = {0xDA, 0xEC};
static uint8_t _client_token_len = 2;

static int _cmd_client(int argc, char **argv)
{
/* Ordered like the RFC method code numbers, but off by 1. GET is code 0. */
Expand All @@ -88,7 +97,6 @@ static int _cmd_client(int argc, char **argv)
uint8_t buf[buflen];
coap_pkt_t pkt;
size_t len;
uint8_t token[2] = {0xDA, 0xEC};

if (argc == 1) {
/* show help for commands */
Expand All @@ -109,7 +117,8 @@ static int _cmd_client(int argc, char **argv)

/* parse options */
if (argc == 5 || argc == 6) {
ssize_t hdrlen = coap_build_hdr(pkt.hdr, COAP_TYPE_CON, &token[0], 2,
ssize_t hdrlen = coap_build_hdr(pkt.hdr, COAP_TYPE_CON,
_client_token, _client_token_len,
code_pos+1, 1);
coap_pkt_init(&pkt, &buf[0], buflen, hdrlen);
coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, argv[4], '/');
Expand Down Expand Up @@ -164,9 +173,33 @@ static int _cmd_client(int argc, char **argv)
argv[0]);
return 1;
}

SHELL_COMMAND(client, "CoAP client", _cmd_client);

static int _cmd_client_token(int argc, char **argv){
if (argc != 2) {
printf("Usage: %s <TOKEN_HEX>\n", argv[0]);
return 1;
}

ssize_t tkl = scn_buf_hex(_client_token, sizeof(_client_token),
argv[1], strlen(argv[1]));

if (tkl == -EOVERFLOW) {
puts("Token too long");
return 1;
}

if (tkl < 0) {
puts("Failed to parse token");
return 1;
}

_client_token_len = tkl;

return 0;
}
SHELL_COMMAND(client_token, "Set Token for CoAP client", _cmd_client_token);

static int _blockwise_cb(void *arg, size_t offset, uint8_t *buf,
size_t len, int more)
{
Expand Down
29 changes: 29 additions & 0 deletions tests/unittests/tests-fmt/tests-fmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,34 @@ static void test_scn_u32_hex(void)
TEST_ASSERT_EQUAL_INT(0xab, scn_u32_hex("aB", 9));
}

static void test_scn_buf_hex(void)
{
uint8_t buf[8];
const char *input_invalid = "hallo";
const char *input_valid = "deadbeef";
const uint8_t expected[] = { 0xde, 0xad, 0xbe, 0xef };
const size_t len_valid = strlen(input_valid);
const size_t len_invalid = strlen(input_invalid);
const size_t len_expected = sizeof(expected);

/* invalid due to odd length */
TEST_ASSERT_EQUAL_INT(-EINVAL, scn_buf_hex(buf, sizeof(buf),
input_valid, len_valid - 1));
/* invalid due to non-hex chars */
TEST_ASSERT_EQUAL_INT(-EINVAL, scn_buf_hex(buf, sizeof(buf),
input_invalid, len_invalid));
/* overflow */
TEST_ASSERT_EQUAL_INT(-EOVERFLOW, scn_buf_hex(buf, 2,
input_valid, len_valid));

memset(buf, 0x55, sizeof(buf));
TEST_ASSERT_EQUAL_INT(len_expected, scn_buf_hex(buf, sizeof(buf),
input_valid, len_valid));
TEST_ASSERT(0 == memcmp(expected, buf, len_expected));
/* did not overwrite */
TEST_ASSERT_EQUAL_INT(0x55, buf[len_expected]);
}

static void test_fmt_lpad(void)
{
const char base[] = "abcd";
Expand Down Expand Up @@ -935,6 +963,7 @@ Test *tests_fmt_tests(void)
new_TestFixture(test_fmt_to_lower),
new_TestFixture(test_scn_u32_dec),
new_TestFixture(test_scn_u32_hex),
new_TestFixture(test_scn_buf_hex),
new_TestFixture(test_fmt_lpad),
};

Expand Down
Loading
Loading