Digest Context Uninitialized Stack Variable in Block Transfer
Environment
- Build System: [CMake]
- Operating System: [Linux]
libcoap Configuration Summary
Last cmake build in ./build
CMake Warning at CMakeLists.txt:984 (message):
Doxygen need to be installed to generate the doxygen documentation
PACKAGE VERSION..................4.3.5
PACKAGE SOURCE...................v4.3.5-134-g690e5fa8
LIBRARY API VERSION..............3
LIBRARY ABI VERSION..............3.2.0
ENABLE_DTLS:.....................ON
ENABLE_TCP:......................ON
ENABLE_IPV4:.....................ON
ENABLE_IPV6:.....................ON
ENABLE_AF_UNIX:..................ON
ENABLE_WEBSOCKETS:...............ON
ENABLE_Q_BLOCK:..................ON
ENABLE_CLIENT_MODE:..............ON
ENABLE_SERVER_MODE:..............ON
ENABLE_OSCORE:...................ON
ENABLE_ASYNC:....................ON
ENABLE_THREAD_SAFE:..............ON
ENABLE_THREAD_RECURSIVE_CHECK....OFF
ENABLE_THREAD_NUM_LOGGING........ON
ENABLE_DOCS:.....................ON
ENABLE_EXAMPLES:.................ON
DTLS_BACKEND:....................default
WITH_GNUTLS:.....................ON
WITH_TINYDTLS:...................OFF
WITH_OPENSSL:....................OFF
WITH_WOLFSSL:....................OFF
WITH_MBEDTLS:....................OFF
HAVE_LIBTINYDTLS:................
HAVE_LIBGNUTLS:..................1
HAVE_LIBOPENSSL:.................
HAVE_LIBWOLFSSL:.................
HAVE_LIBMBEDTLS:.................
WITH_EPOLL:......................ON
WITH_OBSERVE_PERSIST:............ON
BUILD_SHARED_LIBS:...............OFF
MAX_LOGGING_LEVEL:...............8
WARNING_TO_ERROR:................OFF
CMAKE_C_COMPILER:................/root/pcguard-cov/afl-clang-fast
CMAKE_CXX_COMPILER_ID:...........Clang
CMAKE_BUILD_TYPE:................Debug
CMAKE_SYSTEM_PROCESSOR:..........x86_64
CMAKE_HOST_SYSTEM_NAME:..........Linux
CMAKE_GENERATOR:.................Unix Makefiles
Problem Description
Uninitialized crypto context causes block transfer memory error. May cause server crashes, data corruption, or sensitive information leaks. Uninitialized memory could be exploited for Denial-of-Service (DoS) or Remote Code Execution (RCE) attacks.
Steps to reproduce
1.g++ -std=c++11 retest.cpp -o retest
2../retest
Code to reproduce this issue
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iomanip>
#include <sstream>
// Convert hexadecimal string to byte array
std::vector<uint8_t> hex_to_bytes(const std::string& hex) {
std::vector<uint8_t> bytes;
for (size_t i = 0; i < hex.length(); i += 2) {
std::string byteString = hex.substr(i, 2);
uint8_t byte = static_cast<uint8_t>(strtol(byteString.c_str(), nullptr, 16));
bytes.push_back(byte);
}
return bytes;
}
// Convert byte array to hexadecimal string (with space separation)
std::string bytes_to_hex(const std::vector<uint8_t>& bytes) {
std::ostringstream oss;
for (size_t i = 0; i < bytes.size(); ++i) {
oss << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(bytes[i]);
if (i < bytes.size() - 1) {
oss << " ";
}
}
return oss.str();
}
int main() {
const char* server_ip = "127.0.0.1";
const int server_port = 5683;
const std::string hex_data = "012003010105ca7216332b2e77656c6c2d6b6e6f776e04636f7265";
try {
// Convert hexadecimal data to binary
std::vector<uint8_t> binary_data = hex_to_bytes(hex_data);
// Create TCP socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
throw std::runtime_error("Socket creation failed");
}
// Configure server address
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) {
close(sockfd);
throw std::runtime_error("Invalid IP address");
}
// Connect to server
if (connect(sockfd, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
close(sockfd);
throw std::runtime_error("Connection failed");
}
std::cout << "Connected to " << server_ip << ":" << server_port << "\n";
// Send data
if (send(sockfd, binary_data.data(), binary_data.size(), 0) < 0) {
close(sockfd);
throw std::runtime_error("Send operation failed");
}
std::cout << "Sent " << binary_data.size() << " bytes of data:\n"
<< hex_data << "\n";
// Set receive timeout (5 seconds)
timeval timeout{};
timeout.tv_sec = 5;
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
// Receive response
uint8_t buffer[1024];
ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
if (bytes_received > 0) {
std::vector<uint8_t> response(buffer, buffer + bytes_received);
std::cout << "\nReceived " << bytes_received << " byte response:\n"
<< bytes_to_hex(response) << "\n";
} else if (bytes_received == 0) {
std::cout << "Connection closed, no response received\n";
} else {
std::cout << "Response timeout\n";
}
close(sockfd);
} catch (const std::exception& e) {
std::cerr << "Error occurred: " << e.what() << "\n";
return 1;
}
return 0;
}
Logs
root@6dd763ee24d9:~/libcoap/build# ./coap-server
pc_guard: [INIT] bitmap has been allocted from addr 7025564 to 7071488 [ 11481 bits - ratio: 100%]
Shared Memory: (null)
==3535==WARNING: MemorySanitizer: use-of-uninitialized-value
#0 0x4cf35e in coap_add_data_large_internal /root/libcoap/src/coap_block.c:851:13
#1 0x4d0168 in coap_add_data_large_response_lkd /root/libcoap/src/coap_block.c:1403:8
#2 0x4ae330 in hnd_get_index /root/libcoap/examples/coap-server.c:289:3
#3 0x5545df in handle_request /root/libcoap/src/coap_net.c
#4 0x53ccf5 in coap_dispatch /root/libcoap/src/coap_net.c:4783:7
#5 0x539348 in coap_read_session /root/libcoap/src/coap_net.c:2552:15
#6 0x547463 in coap_io_do_epoll_lkd /root/libcoap/src/coap_net.c:2844:13
#7 0x51b6cd in coap_io_process_with_fds_lkd /root/libcoap/src/coap_io.c:2154:5
#8 0x4a606f in main /root/libcoap/examples/coap-server.c:2741:20
#9 0x7f343024a082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)
#10 0x4229fd in _start (/root/libcoap/build/coap-server+0x4229fd)
Uninitialized value was created by an allocation of 'digest_ctx' in the stack frame of function 'coap_digest_setup'
#0 0x62aea0 in coap_digest_setup /root/libcoap/src/coap_gnutls.c:3006
SUMMARY: MemorySanitizer: use-of-uninitialized-value /root/libcoap/src/coap_block.c:851:13 in coap_add_data_large_internal
Exiting
Thanks for debugging and raising this.
What version of the GnuTLS library are you using?
- It appears that gnutls_hash_init() is not initializing the whole of the allocated digest_ctx
- I am unable to reproduce it with GnuTLS version 3.8.3
This can be found by coap-server -? 2>&1 | head -7 .
Thank you for your detailed analysis. The GnuTLS version is 3.6.13. Here's the version information from my environment:
./coap-server: invalid option -- '?'
coap-server v4.3.5 -- a small CoAP implementation
(c) 2010,2011,2015-2025 Olaf Bergmann <[email protected]> and others
Build: v4.3.5-134-g690e5fa8
TLS Library: GnuTLS - runtime 3.6.13, libcoap built for 3.6.13
(DTLS and TLS support; PSK, PKI, PKCS11, RPK and no CID support)
Still unable to reproduce this.
Where the code fails in src/coap_block.c on line 851, we have
849: coap_digest_ctx_t *dctx = coap_digest_setup();
850:
851: if (dctx) {
which means the value in dctx is being reported as uninitialized.
In src/coap_gnutls.c
3005: coap_digest_ctx_t *
3006: coap_digest_setup(void) {
3007: gnutls_hash_hd_t digest_ctx;
3008:
3009: if (gnutls_hash_init(&digest_ctx, GNUTLS_DIG_SHA256)) {
3010: return NULL;
3011: }
3012: return digest_ctx;
3013: }
gnutls_hash_hd_t is actually a pointer (typedef struct hash_hd_st *gnutls_hash_hd_t;), so afl-clang-fast thinks that the pointer digest_ctx does not get updated in the call to gnutls_hash_init() (which is not instrumented). If gnutls_hash_init() returns a non zero value, NULL is returned and so dctx will be initialized. Note that coap_digest_ctx_t * is actually simply a void *.
In the GnuTLS library we have
int
gnutls_hash_init(gnutls_hash_hd_t * dig,
gnutls_digest_algorithm_t algorithm)
{
if (is_mac_algo_forbidden(algorithm))
return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM);
*dig = gnutls_malloc(sizeof(digest_hd_st));
So *dig (digest_ctx) does get updated (and hence dctx initialized) or there is a return value of GNUTLS_E_UNWANTED_ALGORITHM (-22). gnutls_assert_val() just outputs a diagnostic message returning a value of the passed in parameter.
I think afl-clang-fast is reporting a false positive by not thinking that gnutls_has_init() has updated digest_ctx.
Can you please try this work around
diff --git a/src/coap_gnutls.c b/src/coap_gnutls.c
index 8327e5a9..0e826e8d 100644
--- a/src/coap_gnutls.c
+++ b/src/coap_gnutls.c
@@ -3004,7 +3004,7 @@ coap_tls_read(coap_session_t *c_session, uint8_t *data, size_t data_len) {
#if COAP_SERVER_SUPPORT
coap_digest_ctx_t *
coap_digest_setup(void) {
- gnutls_hash_hd_t digest_ctx;
+ gnutls_hash_hd_t digest_ctx = NULL;
if (gnutls_hash_init(&digest_ctx, GNUTLS_DIG_SHA256)) {
return NULL;
When compiling with afl-clang-fast (Ubuntu 24, pkg afl++) and then running ./coap-server, I do not get the header lines
pc_guard: [INIT] bitmap has been allocted from addr 7025564 to 7071488 [ 11481 bits - ratio: 100%]
Shared Memory: (null)
which puzzles me a bit.
build
using MSan
cd /root/libcoap
mkdir build
cd build
##
cmake .. \
-DCMAKE_C_COMPILER=/usr/bin/clang \
-DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
-DCMAKE_C_FLAGS="-Wall -O1 -g -fno-omit-frame-pointer -fsanitize=memory -fsanitize-memory-track-origins -fsanitize-coverage=trace-pc-guard" \
-DCMAKE_CXX_FLAGS="-Wall -O1 -g -fno-omit-frame-pointer -fsanitize=memory -fsanitize-memory-track-origins -fsanitize-coverage=trace-pc-guard" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory" \
-DENABLE_TESTS=OFF \
-DCMAKE_BUILD_TYPE=Debug
##
make
env
OS: Ubuntu 24.04.2 LTS CoAP server: libcoap v4.3.5-135-gf438c61b GnuTLS version: 3.8.3
root@68f4c82dfb92:~/libcoap/build# ./coap-server -? 2>&1 | head -7
./coap-server: invalid option -- '?'
coap-server v4.3.5 -- a small CoAP implementation
(c) 2010,2011,2015-2025 Olaf Bergmann <[email protected]> and others
Build: v4.3.5-135-gf438c61b
TLS Library: GnuTLS - runtime 3.8.3, libcoap built for 3.8.3
(DTLS and TLS support; PSK, PKI, PKCS11, RPK and no CID support)
root@68f4c82dfb92:~/libcoap/build# cat /etc/issue
Ubuntu 24.04.2 LTS \n \l
Logs
root@68f4c82dfb92:~/libcoap/build# ./coap-server
==11981==WARNING: MemorySanitizer: use-of-uninitialized-value
#0 0x56361c5852e0 in coap_digest_setup /root/libcoap/src/coap_gnutls.c:3013:1
#1 0x56361c49e2a9 in coap_add_data_large_internal /root/libcoap/src/coap_block.c:849:36
#2 0x56361c4a1675 in coap_add_data_large_response_lkd /root/libcoap/src/coap_block.c:1403:8
#3 0x56361c48cecb in hnd_get_index /root/libcoap/examples/coap-server.c:289:3
#4 0x56361c4f8048 in handle_request /root/libcoap/src/coap_net.c
#5 0x56361c4ea102 in coap_dispatch /root/libcoap/src/coap_net.c:4783:7
#6 0x56361c4e746b in coap_read_session /root/libcoap/src/coap_net.c:2552:15
#7 0x56361c4ef48b in coap_io_do_epoll_lkd /root/libcoap/src/coap_net.c:2844:13
#8 0x56361c4d38e1 in coap_io_process_with_fds_lkd /root/libcoap/src/coap_io.c:2154:5
#9 0x56361c4880d4 in main /root/libcoap/examples/coap-server.c:2741:20
#10 0x7f904b0631c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 282c2c16e7b6600b0b22ea0c99010d2795752b5f)
#11 0x7f904b06328a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 282c2c16e7b6600b0b22ea0c99010d2795752b5f)
#12 0x56361c3e9a24 in _start (/root/libcoap/build/coap-server+0x36a24) (BuildId: 90df580774734d63011f3d24f29f051bc1e66bd3)
Uninitialized value was stored to memory at
#0 0x56361c5852d9 in coap_digest_setup /root/libcoap/src/coap_gnutls.c:3009:7
#1 0x56361c49e2a9 in coap_add_data_large_internal /root/libcoap/src/coap_block.c:849:36
#2 0x56361c4a1675 in coap_add_data_large_response_lkd /root/libcoap/src/coap_block.c:1403:8
#3 0x56361c48cecb in hnd_get_index /root/libcoap/examples/coap-server.c:289:3
#4 0x56361c4f8048 in handle_request /root/libcoap/src/coap_net.c
#5 0x56361c4ea102 in coap_dispatch /root/libcoap/src/coap_net.c:4783:7
#6 0x56361c4e746b in coap_read_session /root/libcoap/src/coap_net.c:2552:15
#7 0x56361c4ef48b in coap_io_do_epoll_lkd /root/libcoap/src/coap_net.c:2844:13
#8 0x56361c4d38e1 in coap_io_process_with_fds_lkd /root/libcoap/src/coap_io.c:2154:5
#9 0x56361c4880d4 in main /root/libcoap/examples/coap-server.c:2741:20
#10 0x7f904b0631c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 282c2c16e7b6600b0b22ea0c99010d2795752b5f)
#11 0x7f904b06328a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 282c2c16e7b6600b0b22ea0c99010d2795752b5f)
#12 0x56361c3e9a24 in _start (/root/libcoap/build/coap-server+0x36a24) (BuildId: 90df580774734d63011f3d24f29f051bc1e66bd3)
Uninitialized value was created by an allocation of 'digest_ctx' in the stack frame
#0 0x56361c585298 in coap_digest_setup /root/libcoap/src/coap_gnutls.c:3007:3
SUMMARY: MemorySanitizer: use-of-uninitialized-value /root/libcoap/src/coap_gnutls.c:3013:1 in coap_digest_setup
Exiting
Thanks for the details on how to compile to get this issue.
The bottom line is that this is throwing up a lot of issues within the TLS libraries, which are outside of the control of libcoap. As the TLS libraries are not instrumented, the memcpy() command in particular is not instrumented and so memcpy() updating data is not recognized.
If the reported issue is fixed in libcoap by setting digest_ctx to NULL, and the secondary issue that then occurs which is fixed by setting digest to all zeros, things then blow up (in the client) when trying to send the (D)TLS "Client Hello" generated by GnuTLS.
In a similar vein, OpenSSL and WolfSSL all blow up in code internal to the respective libraries. Only MbedTLS and TinyDTLS appear to work as is, but have only done limited testing.
Thanks for the detailed explanation.
Closing as invoked third-party libraries are unfortunately not not instrumented.