Fix pcl-zqv.4.2: protocol parsing and buffer safety in prpc/papi (#348)

prpc.c:
- on_request: fix total_size computation. The original formula
  sizeof(uint32_t)+sizeof(uint64_t)+response->length had two bugs:
  (1) sizeof(uint32_t)+sizeof(uint64_t)=12 but offsetof(rpc_message_t,value)=16
  due to struct alignment padding between the uint32_t type and uint64_t length
  fields; and (2) respond() was storing full message size in response->length,
  double-counting the header. Fix: use offsetof(rpc_message_t,value)+response->length,
  consistent with readResponse() in rpcclient.cpp which reads a fixed header_size
  of offsetof(rpc_message_t,value) bytes then reads msg->length payload bytes.
- respond: store payload length only in response->length (value_length+1),
  not full message size, to match the client protocol expectation.
- prpc_init: add null check on malloc return value.
- prpc_register: restore old handler table and return -1 if prpc_init fails.

papi.c:
- papi_result_thread: add missing MAX_API_RESPONSE_SIZE guard (present in
  papi_result but absent here), preventing server-controlled unbounded malloc.
- papi_result, papi_result_thread: add null checks on malloc before passing
  pointer to psock_readall.
- papi_result_async: add MAX_API_RESPONSE_SIZE check and malloc null check
  on reader->respsize path.
- calc_ret_len: add _NEED_DATA(1) guard before ARRAY and HASH while-loop
  conditions; empty containers with datalen=0 caused out-of-bounds read.

Co-authored-by: Levi Neely <lkn@darkstar.example.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-04 19:02:01 +01:00 committed by GitHub
parent 07e4189bae
commit 874e25f11b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 2 deletions

View File

@ -201,6 +201,7 @@ static ssize_t calc_ret_len(unsigned char **restrict data,
int unsigned cnt; int unsigned cnt;
cnt = 0; cnt = 0;
ret = sizeof(binresult); ret = sizeof(binresult);
_NEED_DATA(1);
while (**data != RPARAM_END) { while (**data != RPARAM_END) {
r = calc_ret_len(data, datalen, strcnt); r = calc_ret_len(data, datalen, strcnt);
if (r == -1) if (r == -1)
@ -218,6 +219,7 @@ static ssize_t calc_ret_len(unsigned char **restrict data,
int unsigned cnt; int unsigned cnt;
cnt = 0; cnt = 0;
ret = sizeof(binresult); ret = sizeof(binresult);
_NEED_DATA(1);
while (**data != RPARAM_END) { while (**data != RPARAM_END) {
r = calc_ret_len(data, datalen, strcnt); r = calc_ret_len(data, datalen, strcnt);
if (r == -1) if (r == -1)
@ -405,6 +407,10 @@ binresult *papi_result(psock_t *sock) {
} }
data = (unsigned char *)malloc(ressize); data = (unsigned char *)malloc(ressize);
if (!data) {
pdbg_logf(D_ERROR, "Failed to allocate %u bytes for API response", ressize);
return NULL;
}
if (pdbg_unlikely(psock_readall(sock, data, ressize) != ressize)) { if (pdbg_unlikely(psock_readall(sock, data, ressize) != ressize)) {
free(data); free(data);
@ -424,7 +430,16 @@ binresult *papi_result_thread(psock_t *sock) {
if (pdbg_unlikely(psock_readall_thread( if (pdbg_unlikely(psock_readall_thread(
sock, &ressize, sizeof(uint32_t)) != sizeof(uint32_t))) sock, &ressize, sizeof(uint32_t)) != sizeof(uint32_t)))
return NULL; return NULL;
if (ressize > MAX_API_RESPONSE_SIZE) {
pdbg_logf(D_WARNING, "API response size %u exceeds limit %u, rejecting",
ressize, MAX_API_RESPONSE_SIZE);
return NULL;
}
data = (unsigned char *)malloc(ressize); data = (unsigned char *)malloc(ressize);
if (!data) {
pdbg_logf(D_ERROR, "Failed to allocate %u bytes for API response", ressize);
return NULL;
}
if (pdbg_unlikely(psock_readall_thread(sock, data, ressize) != if (pdbg_unlikely(psock_readall_thread(sock, data, ressize) !=
ressize)) { ressize)) {
free(data); free(data);
@ -467,8 +482,17 @@ again:
reader->state = 1; reader->state = 1;
reader->bytesread = 0; reader->bytesread = 0;
reader->bytestoread = reader->respsize; reader->bytestoread = reader->respsize;
if (reader->respsize > MAX_API_RESPONSE_SIZE) {
pdbg_logf(D_WARNING, "API response size %u exceeds limit %u, rejecting",
reader->respsize, MAX_API_RESPONSE_SIZE);
reader->result = NULL;
papi_rdr_alloc(reader);
return ASYNC_RES_READY;
}
reader->data = (unsigned char *)malloc(reader->respsize); reader->data = (unsigned char *)malloc(reader->respsize);
if (!reader->data) { if (!reader->data) {
pdbg_logf(D_ERROR, "Failed to allocate %u bytes for API response",
reader->respsize);
reader->result = NULL; reader->result = NULL;
papi_rdr_alloc(reader); papi_rdr_alloc(reader);
return ASYNC_RES_READY; return ASYNC_RES_READY;

View File

@ -36,6 +36,7 @@
*/ */
#include <errno.h> #include <errno.h>
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -108,7 +109,7 @@ static void on_request(void *lpvParam) {
if (request) { if (request) {
respond(request, response); respond(request, response);
ssize_t total_size = sizeof(uint32_t) + sizeof(uint64_t) + response->length; ssize_t total_size = offsetof(rpc_message_t, value) + response->length;
ssize_t bytes_written = write(*sockfd, response, total_size); ssize_t bytes_written = write(*sockfd, response, total_size);
if (bytes_written == -1) { if (bytes_written == -1) {
@ -229,7 +230,7 @@ static void respond(rpc_message_t *request, rpc_message_t *response) {
response->value[value_length] = '\0'; response->value[value_length] = '\0';
pdbg_logf(D_WARNING, "Response message truncated to fit buffer"); pdbg_logf(D_WARNING, "Response message truncated to fit buffer");
} }
response->length = sizeof(rpc_message_t) + value_length + 1; response->length = value_length + 1;
} }
void prpc_main_loop() { void prpc_main_loop() {
@ -297,6 +298,11 @@ int prpc_register(int cmdid, prpc_handler h) {
if (cmdid > (calbacks_lower_band + handlers_size)) { if (cmdid > (calbacks_lower_band + handlers_size)) {
handlers_size = cmdid - calbacks_lower_band + 1; handlers_size = cmdid - calbacks_lower_band + 1;
prpc_init(); prpc_init();
if (!handlers) {
handlers = handlers_old;
handlers_size = handlers_size_old;
return -1;
}
memcpy(handlers, handlers_old, memcpy(handlers, handlers_old,
handlers_size_old * sizeof(prpc_handler)); handlers_size_old * sizeof(prpc_handler));
free(handlers_old); free(handlers_old);
@ -307,6 +313,10 @@ int prpc_register(int cmdid, prpc_handler h) {
void prpc_init() { void prpc_init() {
handlers = (prpc_handler *)malloc(sizeof(prpc_handler) * handlers_size); handlers = (prpc_handler *)malloc(sizeof(prpc_handler) * handlers_size);
if (!handlers) {
pdbg_logf(D_ERROR, "Failed to allocate handler table");
return;
}
memset(handlers, 0, sizeof(prpc_handler) * handlers_size); memset(handlers, 0, sizeof(prpc_handler) * handlers_size);
} }