Skip to content

[BUG] proto_smpp: out-of-bounds read in PDU body parsing due to missing bounds checks #3847

@jming912

Description

@jming912

OpenSIPS version you are running

Latest master (commit a74fc46, 2026-03-19)

Describe the bug

The SMPP PDU body parsing functions parse_submit_or_deliver_body() and parse_bind_receiver_body() in modules/proto_smpp/smpp.c advance a pointer through the received data without any bounds checking against the PDU's command_length. A crafted PDU with a short command_length but no null terminators in the body causes the parser to read far beyond the actual received data.

parse_submit_or_deliver_body() (smpp.c, line ~955):

static void parse_submit_or_deliver_body(smpp_submit_sm_t *body,
    smpp_header_t *header, char *buffer)
{
    char *p = buffer;
    p += copy_var_str(body->service_type, p, MAX_SERVICE_TYPE_LEN);
    body->source_addr_ton = *p++;
    body->source_addr_npi = *p++;
    p += copy_var_str(body->source_addr, p, MAX_ADDRESS_LEN);
    // ... more fields, no bounds checking ...
    body->sm_length = *p++;
    copy_fixed_str(body->short_message, p, body->sm_length);
}

The header parameter is received but never used — there is no check that p stays within command_length - HEADER_SZ bytes. Each copy_var_str() scans for a null byte or until maxlen, so if the data has no null terminators the pointer advances past the actual PDU data.

parse_bind_receiver_body() (smpp.c, line ~983) has the same pattern and is reachable as the first PDU on an SMPP connection (bind request), before any authentication.

To Reproduce

// Compile: clang -fsanitize=address -g -o poc_oob poc_oob.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#define MAX_SERVICE_TYPE_LEN 6
#define MAX_ADDRESS_LEN 21
#define MAX_SCHEDULE_DELIVERY_LEN 1
#define MAX_VALIDITY_PERIOD 1

typedef struct {
    char service_type[MAX_SERVICE_TYPE_LEN];
    uint8_t source_addr_ton, source_addr_npi;
    char source_addr[MAX_ADDRESS_LEN];
    uint8_t dest_addr_ton, dest_addr_npi;
    char destination_addr[MAX_ADDRESS_LEN];
    uint8_t esm_class, protocol_id, protocol_flag;
    char schedule_delivery_time[MAX_SCHEDULE_DELIVERY_LEN];
    char validity_period[MAX_VALIDITY_PERIOD];
    uint8_t registered_delivery, replace_if_present_flag;
    uint8_t data_coding, sm_default_msg_id, sm_length;
    char short_message[254];
} smpp_submit_sm_t;

int copy_var_str(char *to, char *from, int maxlen) {
    int iret = 1;
    while (*from && maxlen--) { *to++ = *from++; iret++; }
    *to++ = '\0';
    return iret;
}
int copy_fixed_str(char *to, char *from, int n) {
    int r = n; while (n--) *to++ = *from++; return r;
}

void parse_submit_or_deliver_body(smpp_submit_sm_t *body, char *buffer) {
    char *p = buffer;
    p += copy_var_str(body->service_type, p, MAX_SERVICE_TYPE_LEN);
    body->source_addr_ton = *p++;
    body->source_addr_npi = *p++;
    p += copy_var_str(body->source_addr, p, MAX_ADDRESS_LEN);
    body->dest_addr_ton = *p++;
    body->dest_addr_npi = *p++;
    p += copy_var_str(body->destination_addr, p, MAX_ADDRESS_LEN);
    body->esm_class = *p++;
    body->protocol_id = *p++;
    body->protocol_flag = *p++;
    p += copy_var_str(body->schedule_delivery_time, p, MAX_SCHEDULE_DELIVERY_LEN);
    p += copy_var_str(body->validity_period, p, MAX_VALIDITY_PERIOD);
    body->registered_delivery = *p++;
    body->replace_if_present_flag = *p++;
    body->data_coding = *p++;
    body->sm_default_msg_id = *p++;
    body->sm_length = *p++;
    copy_fixed_str(body->short_message, p, body->sm_length);
}

int main(void) {
    smpp_submit_sm_t body;
    memset(&body, 0, sizeof(body));
    // PDU body only 10 bytes, no null terminators
    char *buffer = (char *)malloc(10);
    memset(buffer, 'A', 10);
    parse_submit_or_deliver_body(&body, buffer);
    free(buffer);
    return 0;
}

Run:

$ clang -fsanitize=address -g -o poc_oob poc_oob.c && ./poc_oob
==ERROR: AddressSanitizer: heap-buffer-overflow on address ...
READ of size 1 at ... thread T0
    #0 in copy_var_str
    #1 in parse_submit_or_deliver_body
... is located 0 bytes after 10-byte region

Expected behavior

The parsing functions should use header->command_length to validate that the pointer does not advance past the end of the PDU body. Out-of-bounds PDUs should be rejected.

Relevant System Logs

Without ASAN, the parser silently reads stale/uninitialized data from the TCP receive buffer (up to 65KB). This can produce garbled field values or crash.

OS/environment information

  • Operating System: Ubuntu 22.04
  • OpenSIPS installation: git (master, commit a74fc46)

Additional context

  • CWE: CWE-125 (Out-of-bounds Read)
  • Attack vector: Network — send a crafted submit_sm, deliver_sm, or bind_transceiver SMPP PDU with a short command_length and no null terminators
  • Authentication: Not required (bind PDU is the first message on a new connection)
  • Severity: High — reads up to ~65KB past the PDU boundary from the TCP receive buffer. Information leak and potential crash (DoS).

Suggested fix — pass command_length into the parse functions and bounds-check before each access:

static int parse_submit_or_deliver_body(smpp_submit_sm_t *body,
    smpp_header_t *header, char *buffer)
{
    char *p = buffer;
    char *end = buffer + (header->command_length - HEADER_SZ);

    if (p >= end) return -1;
    p += copy_var_str(body->service_type, p, MIN(MAX_SERVICE_TYPE_LEN, end - p));
    if (p + 2 > end) return -1;
    body->source_addr_ton = *p++;
    body->source_addr_npi = *p++;
    // ... etc
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions