CVE-2025-53511
A heap-based buffer overflow vulnerability exists in the MFER parsing functionality of The Biosig Project libbiosig 3.9.0 and Master Branch (35a819fa). A specially crafted MFER file can lead to arbitrary code execution. An attacker can provide a malicious file to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
The Biosig Project libbiosig 3.9.0
The Biosig Project libbiosig Master Branch (35a819fa)
libbiosig - https://biosig.sourceforge.net/index.html
9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-122 - Heap-based Buffer Overflow
Libbiosig is an open source library designed to process various types of medical signal data (EKG, EEG, etc) within a vast variety of different file formats. Libbiosig is also at the core of biosig APIs in Octave and Matlab, sigviewer, and other scientific software utilized for interpreting biomedical signal data.
Within libbiosig, the sopen_extended
function is the common entry point for file parsing, regardless of the specific file type:
HDRTYPE* sopen_extended(const char* FileName, const char* MODE, HDRTYPE* hdr, biosig_options_type *biosig_options) {
/*
MODE="r"
reads file and returns HDR
MODE="w"
writes HDR into file
*/
The general flow of sopen_extended
is as one might expect: initialize generic strctures, determine the relevant file type, parse the file, and finally populate the generic structures that can be utilized by whatever is calling sopen_extended
. To determine the file type, sopen_extended
calls getfiletype
, which attempts to fingerprint the file based on the presence of various magic bytes within the header. Libbiosig also allows for these heuristics to be bypassed by setting the file type manually, but that approach is more applicable when writing data to a file; this vulnerability concerns the code path taken when reading from a file.
The file type used to exercise this vulnerability is the Medical waveform Format Encoding Rules (MFER), a file format for encoding medical waveforms from various different kinds of medical devices, such as ECG and EEG. The MFER standard aims to abstract away the encoding of the waveform data itself, independent of the specifics of the recording device.
To determine if the input file is valid MFER, getfiletype
runs the following check:
else if (!memcmp(Header1,"@ MFER ",8))
hdr->TYPE = MFER;
else if (!memcmp(Header1,"@ MFR ",6))
hdr->TYPE = MFER;
Put simply, libbiosig classifies an input file as MFER if the first few bytes match one of two magic byte sequences and stores the file classification in the struct member hdr->TYPE
.
Further along in sopen_extended
, after getfiletype
has returned, hdr->TYPE
is checked again and, if it’s MFER, processing unique to the format is then performed. This code block starts by defining some temporary variables that will be used for parsing the MFER file, including initializing the number of channels (hdr->NS
) to 1 [1], which will become important later. From there, the bulk of the file processing is done inside a while loop [2] that continues so long as there are remaining bytes to be read from the input file, which hdr
is a handle to:
else if (hdr->TYPE==MFER) {
...
uint8_t buf[128];
void* ptrbuf = buf;
uint8_t gdftyp = 3; // default: int16
uint8_t UnitCode=0;
double Cal = 1.0, Off = 0.0;
char SWAP = ( __BYTE_ORDER == __LITTLE_ENDIAN); // default of MFER is BigEndian
hdr->FILE.LittleEndian = 0;
hdr->SampleRate = 1000; // default sampling rate is 1000 Hz
hdr->NS = 1; // default number of channels is 1 // [1]
/* TAG */ // [3]
uint8_t tag = hdr->AS.Header[0];
ifseek(hdr,1,SEEK_SET);
int curPos = 1;
size_t N_EVENT=0; // number of events, memory is allocated for in the event table.
while (!ifeof(hdr)) { // [2]
uint32_t len, val32=0;
int32_t chan=-1;
uint8_t tmplen;
if (tag==255)
break;
else if (tag==63) {
/* CONTEXT */
curPos += ifread(buf,1,1,hdr);
chan = buf[0] & 0x7f;
while (buf[0] & 0x80) {
curPos += ifread(buf,1,1,hdr);
chan = (chan<<7) + (buf[0] & 0x7f);
}
}
/* LENGTH */ // [4]
curPos += ifread(&tmplen,1,1,hdr);
char FlagInfiniteLength = 0;
if ((tag==63) && (tmplen==0x80)) {
FlagInfiniteLength = -1; //Infinite Length
len = 0;
}
else if (tmplen & 0x80) {
tmplen &= 0x7f;
curPos += ifread(&buf,1,tmplen,hdr);
len = 0;
k = 0;
while (k<tmplen)
len = (len<<8) + buf[k++];
}
else
len = tmplen;
if (VERBOSE_LEVEL>7)
fprintf(stdout,"MFER: tag=%3i chan=%2i len=%i %3i curPos=%i %li\n",tag,chan,tmplen,len,curPos,iftell(hdr));
/* VALUE */ // [5]
if (tag==0) {
if (len!=1) fprintf(stderr,"Warning MFER tag0 incorrect length %i!=1\n",len);
curPos += ifread(buf,1,len,hdr);
}
else if (tag==1) {
...
The encoding rules for MFER, as described in ISO-22066-1-2022, specify data should be structured in the form Tag, Data Length, Value, or TLV. Broadly speaking, the Tag portion classifies the data, the Data Length portion encodes the length of the data in octets (bytes), and the Value portion encodes the actual payload. This is reflected in the MFER processing code above, with the relevant blocks for parsing the Tag [3], Length [4], and Value [5] fields annotated appropriately.
The primary job of the TAG block is simply to extract the tag from the first octet of the header, which is then stored in the tag
variable. From there, the LENGTH block reads the length from the input file into len
, which can be encoded as a single octet, multiple octets, or an “indefinite data length” flag, where the data is instead terminated via a special “end-of-contents” frame. Finally, the bulk of the processing is performed in the VALUE block. The meaning of the data is dependent on the tag, so this code largely consists of numerous conditional statements that perform unique processing depending on the value of tag
. That said, this vulnerability is exercised in the code responsbile for processing channel data, which occurs after the aforementioned while loop has completed and all the frames have been parsed:
hdr->AS.bpb = 0;
for (k=0; k<hdr->NS; k++) { // [6]
if (VERBOSE_LEVEL>8)
fprintf(stdout,"sopen(MFER): #%i\n",(int)k);
CHANNEL_TYPE *hc = hdr->CHANNEL+k; // [7]
hc->Transducer[0] = 0;
if (!hc->PhysDimCode) hc->PhysDimCode = MFER_PhysDimCodeTable[UnitCode];
if (hc->Cal==1.0) hc->Cal = Cal;
hc->Off = Off * hc->Cal;
if (!hc->SPR) hc->SPR = hdr->SPR;
if (hc->GDFTYP<16)
if (hc->GDFTYP & 0x01) {
hc->DigMax = ldexp( 1.0,GDFTYP_BITS[gdftyp]-1) - 1.0;
hc->DigMin = ldexp(-1.0,GDFTYP_BITS[gdftyp]-1);
}
else {
hc->DigMax = ldexp( 1.0,GDFTYP_BITS[gdftyp]);
hc->DigMin = 0.0;
}
else {
hc->DigMax = INFINITY;
hc->DigMin = -INFINITY;
}
hc->PhysMax = hc->DigMax * hc->Cal + hc->Off;
hc->PhysMin = hc->DigMin * hc->Cal + hc->Off;
hc->OnOff = 1;
hc->bi = hdr->AS.bpb;
hdr->AS.bpb += hdr->SPR*(GDFTYP_BITS[gdftyp]>>3);
}
The channel processing code at the tail end of the MFER processing block is done via a for loop [6] that iterates over each channel one by one, with k
holding the index of the current channel being processed and the total number of channels (hdr->NS
) used as the loop’s upper bound. For context, libbiosig stores channel data in hdr->CHANNEL
, a heap allocated array of CHANNEL_TYPE
structures - one per channel. The CHANNEL_TYPE
(aka CHANNEL_STRUCT
) structure itself is defined in biosig-dev.h:
typedef struct CHANNEL_STRUCT {
double PhysMin ATT_ALI; /* physical minimum */
double PhysMax ATT_ALI; /* physical maximum */
double DigMin ATT_ALI; /* digital minimum */
double DigMax ATT_ALI; /* digital maximum */
double Cal ATT_ALI; /* gain factor */
double Off ATT_ALI; /* bias */
char Label[MAX_LENGTH_LABEL+1] ATT_ALI; /* Label of channel */
char OnOff ATT_ALI; /* 0: channel is off, not consider for data output; 1: channel is turned on; 2: channel containing time axis */
uint16_t LeadIdCode ATT_ALI; /* Lead identification code */
char Transducer[MAX_LENGTH_TRANSDUCER+1] ATT_ALI; /* transducer e.g. EEG: Ag-AgCl electrodes */
#ifdef MAX_LENGTH_PHYSDIM
char PhysDim[MAX_LENGTH_PHYSDIM+1] ATT_ALI ATT_DEPREC; /* DONOT USE - use PhysDim3(PhysDimCode) instead */
#endif
uint16_t PhysDimCode ATT_ALI; /* code for physical dimension - PhysDim3(PhysDimCode) returns corresponding string */
float TOffset ATT_ALI; /* time delay of sampling */
float LowPass ATT_ALI; /* lowpass filter */
float HighPass ATT_ALI; /* high pass */
float Notch ATT_ALI; /* notch filter */
float XYZ[3] ATT_ALI; /* sensor position */
union {
/* context specific channel information */
float Impedance ATT_ALI; /* Electrode Impedance in Ohm, defined only if PhysDim = _Volt */
float fZ ATT_ALI; /* ICG probe frequency, defined only if PhysDim = _Ohm */
} ATT_ALI;
/* this part should not be used by application programs */
uint8_t* bufptr ATT_ALI; /* pointer to buffer: NRec<=1 and bi,bi8 not used */
uint32_t SPR ATT_ALI; /* samples per record (block) */
uint32_t bi ATT_ALI; /* start byte (byte index) of channel within data block */
uint32_t bi8 ATT_ALI; /* start bit (bit index) of channel within data block */
uint16_t GDFTYP ATT_ALI; /* data type */
} CHANNEL_TYPE ATT_ALI ATT_MSSTRUCT;
With that context established, we can see that the channel processing for loop begins by saving a pointer to the current CHANNEL_TYPE
structure being processed to the local variable hc
[7], which is then used as a handle to write to various members of that struct. Unfortunately, this leads to a heap-based buffer overflow vulnerability due to a mismatch between the initial size allocated for the hdr->CHANNEL
array and the default value of hdr->NS
in the MFER processing code.
As mentioned previously, hdr->NS
is initialized to 1 at the start of the MFER processing code and, within this scope, is only changed from this default value when parsing a frame using tag 5:
else if (tag==5) //0x05: number of channels
{
uint16_t oldNS=hdr->NS;
if (len>4) fprintf(stderr,"Warning MFER tag5 incorrect length %i>4\n",len);
curPos += ifread(buf,1,len,hdr);
hdr->NS = *(int64_t*) mfer_swap8b(buf, len, SWAP);
if (VERBOSE_LEVEL>7) fprintf(stdout,"MFER: TLV %i %i %i \n",tag,len,(int)hdr->NS);
hdr->CHANNEL = (CHANNEL_TYPE*)realloc(hdr->CHANNEL, hdr->NS*sizeof(CHANNEL_TYPE)); // [8]
Based on the call to realloc
in the code block above [8], libbiosig attempts to keep the size of hdr->CHANNEL
synchronized with hdr->NS
, which makes sense since hdr->NS
stores the number of channels and hdr->CHANNEL
stores information for each channel. If this code is never reached during the processing of an MFER file, however, both hdr->NS
and the size of hdr->CHANNEL
will remain at their default values. We already know that hdr->NS
is initialized to 1, but what about the initial size of the hdr->CHANNEL
array? This array, along with the majority of the fields for hdr
, are initialized via the function constructHDR
:
/****************************************************************************/
/** INIT HDR **/
/****************************************************************************/
#define Header1 ((char*)hdr->AS.Header)
HDRTYPE* constructHDR(const unsigned NS, const unsigned N_EVENT)
{
/*
HDR is initialized, memory is allocated for
NS channels and N_EVENT number of events.
The purpose is to define all parameters at an initial step.
No parameters must remain undefined.
*/
HDRTYPE* hdr = (HDRTYPE*)malloc(sizeof(HDRTYPE));
...
hdr->NS = NS; // [10]
...
// define variable header
hdr->CHANNEL = (CHANNEL_TYPE*)calloc(hdr->NS, sizeof(CHANNEL_TYPE)); // [9]
...
return(hdr);
}
From the definition of constructHDR
, we can see that the memory for the hdr->CHANNEL
array is allocated on the heap using a call to calloc
[9], with sizeof(CHANNEL_TYPE)
used as the size of each element and hdr->NS
as the number of elements. Further up in constructHDR
, hdr->NS
is initialized to NS
[10], the first argument passed to the function, so it is this argument that ultimately controls the allocated size of hdr->CHANNEL
.
Looking at the only place this function is called within biosig.c, we can see that this argument is set to 0:
if (hdr==NULL)
hdr = constructHDR(0,0); // initializes fields that may stay undefined during SOPEN
Thus, the default size of hdr->CHANNEL
is obtained by calling calloc
with the number of elements set to 0. Technically speaking, the behavior of calloc
in such a scenario is implementation-defined, but on many platforms (including the x86-64 Linux platform used in our testing), this results an allocation of the smallest size possible. Zooming out and comparing the default state for hdr->NS
and hdr->CHANNEL
quickly reveals the problem: the MFER processing code will assume at least 1 channel is present (and thus at least one entry in the hdr->CHANNEL
array) even if hdr->CHANNEL
hasn’t been resized since its initial “0” size allocation via constructHDR
.
This can be demonstrated dynamically by supplying the attached POC file to libbiosig and attaching a debugger:
Breakpoint 2, sopen_extended (FileName=<optimized out>, MODE=<optimized out>, hdr=0x519000001980, biosig_options=<optimized out>)
at biosig.c:9246
9246 for (k=0; k<hdr->NS; k++) {
────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────
In file: /home/mbereza/Projects/BioSig/biosig-code/biosig4c++/biosig.c:9246
9241 int sz=ifread(&tag,1,1,hdr);
9242 curPos += sz;
9243 }
9244 hdr->FLAG.OVERFLOWDETECTION = 0; // overflow detection OFF - not supported
9245 hdr->AS.bpb = 0;
► 9246 for (k=0; k<hdr->NS; k++) {
9247
9248 if (VERBOSE_LEVEL>8)
9249 fprintf(stdout,"sopen(MFER): #%i\n",(int)k);
9250
9251 CHANNEL_TYPE *hc = hdr->CHANNEL+k;
──────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────
► 0 0x7ffff73339a5 sopen_extended+211637
1 0x5555555554c8 main+511
2 0x7ffff6a2a1ca __libc_start_call_main+122
3 0x7ffff6a2a28b __libc_start_main+139
4 0x555555555205 _start+37
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> p/x hdr->NS
$1 = 0x1
pwndbg> p/x hdr->CHANNEL
$2 = 0x502000000190 // [11]
pwndbg> heap -v 0x502000000180
Allocated chunk | PREV_INUSE
Addr: 0x502000000180
prev_size: 0x00
size: 0x20 (with flag bits: 0x21) // [12]
...
pwndbg> p/x sizeof(CHANNEL_TYPE)
$3 = 0x158 // [13]
Breaking right before entering the for loop that performs the channel processing, we can see from the GDB output above that hdr->NS
is still at its default value of 1. Meanwhile, hdr->CHANNEL
is located at the address 0x502000000190 [11] as has an allocated size of only 16 bytes (GDB shows the size as 0x20, but 16 of these bytes are reserved for heap metadata, making the total usable size 16 bytes) [12]. Given that this is substantially smaller than the size of a single CHANNEL_TYPE
structure (344 bytes) [13], it’s safe to say hdr->CHANNEL
hasn’t been resized since it’s initial 0-sized allocation via constructHDR
.
Some quick tests via GDB reveal that attempting to access any members of the “first” CHANNEL_TYPE
struct besides the first two (PhysMin
and PhysMax
) will result in an out-of-bounds access due to the tiny size allocated for hdr->CHANNEL
:
pwndbg> print (int)&((CHANNEL_TYPE*)0)->PhysMin
$4 = 0
pwndbg> print (int)&((CHANNEL_TYPE*)0)->PhysMax
$5 = 8
pwndbg> print (int)&((CHANNEL_TYPE*)0)->DigMin
$6 = 16
Unfortunately, this means that almost every write performed in the body of the channel processing for loop will result in a heap-based buffer overflow condition, as almost all of these write to a CHANNEL_TYPE
struct member outside the range of allocated memory. The remainder of this analysis will focus on the first example encountered, located on line 9252 of biosig.c:
for (k=0; k<hdr->NS; k++) {
...
CHANNEL_TYPE *hc = hdr->CHANNEL+k;
hc->Transducer[0] = 0;
Since we’ve already established that hdr->NS
, the upper bound for the for loop, is 1, we know that hc
is equal to hdr->CHANNEL[0]
. Thus, the vulnerable line of code attempts to write a null byte to hdr->CHANNEL[0].Transducer[0]
. Since the Transducer
member is located at an offset of 152 bytes into the CHANNEL_TYPE
struct:
pwndbg> print (int)&((CHANNEL_TYPE*)0)->Transducer
$7 = 152
the writing of this null byte should result in a heap-based buffer overflow. Sure enough, continuing execution from here trips AddressSantizer with a heap-based buffer overflow condition:
pwndbg> c
Continuing.
=================================================================
==49789==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000228 at pc 0x7ffff734fa41 bp 0x7fffffffa7d0 sp 0x7fffffffa7c0
WRITE of size 1 at 0x502000000228 thread T0
#0 0x7ffff734fa40 in sopen_extended /home/mbereza/Projects/BioSig/biosig-code/biosig4c++/biosig.c:9252
#1 0x5555555554c7 in main harness.cpp:38
#2 0x7ffff6a2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#3 0x7ffff6a2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#4 0x555555555204 in _start (/home/mbereza/Projects/BioSig/repro_master/harness+0x1204) (BuildId: 71453a64ceb3bfa137fcadc13ca9ee9004d4faa3)
0x502000000228 is located 151 bytes after 1-byte region [0x502000000190,0x502000000191)
allocated by thread T0 here:
#0 0x7ffff78fd340 in calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:77
#1 0x7ffff72cbf53 in constructHDR /home/mbereza/Projects/BioSig/biosig-code/biosig4c++/biosig.c:1321
#2 0x555555555411 in main harness.cpp:35
#3 0x7ffff6a2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#4 0x7ffff6a2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#5 0x555555555204 in _start (/home/mbereza/Projects/BioSig/repro_master/harness+0x1204) (BuildId: 71453a64ceb3bfa137fcadc13ca9ee9004d4faa3)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/mbereza/Projects/BioSig/biosig-code/biosig4c++/biosig.c:9252 in sopen_extended
Shadow bytes around the buggy address:
0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x502000000000: fa fa 00 00 fa fa 00 fa fa fa 00 fa fa fa fd fa
0x502000000080: fa fa fd fd fa fa fd fd fa fa 00 00 fa fa 00 fa
0x502000000100: fa fa 04 fa fa fa fd fd fa fa 00 00 fa fa 00 fa
0x502000000180: fa fa 01 fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x502000000200: fa fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa fa
0x502000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
==49789==ABORTING
[Inferior 1 (process 49789) exited with code 01]
pwndbg> p/x 0x502000000228 - 0x502000000190
$8 = 0x98 // [14]
As expected, the overflow occurs on line 9252 of biosig.c, the write is of size 1, and the destination address of the write (0x502000000228) is located exactly 152 (0x98) bytes past the previously computed address of hdr->CHANNEL
(0x502000000190) [14]. With this specific POC, the attacker doesn’t have much control of the write destination, as it occurs at a fixed offset from the hdr->CHANNEL
address, nor the data written, as it is always a single null byte. This doesn’t hold true for every manifestation of this vulnerability, however, as some write data that is computed based on data from the input file, making it at least partially attacker-controlled. One such example occurs on line 9256 of biosig.c:
for (k=0; k<hdr->NS; k++) {
...
CHANNEL_TYPE *hc = hdr->CHANNEL+k;
...
if (!hc->SPR) hc->SPR = hdr->SPR;
This results in a write to the SPR
member of the CHANNEL_TYPE
struct if the current value is 0. The data written is the value of hdr->SPR
, which can be set using data from the input file by sending a frame with tag 4:
else if (tag==4) {
// SPR
if (len>4) fprintf(stderr,"Warning MFER tag4 incorrect length %i>4\n",len);
curPos += ifread(buf,1,len,hdr);
hdr->SPR = *(int64_t*) mfer_swap8b(buf, len, SWAP);
Thus, this vulnerability allows for a heap-based buffer overflow where the data written is controlled by the attacker. Depending on the setup of the heap, this flaw can potentially lead to arbitrary code execution.
==49789==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000228 at pc 0x7ffff734fa41 bp 0x7fffffffa7d0 sp 0x7fffffffa7c0
WRITE of size 1 at 0x502000000228 thread T0
#0 0x7ffff734fa40 in sopen_extended /home/mbereza/Projects/BioSig/biosig-code/biosig4c++/biosig.c:9252
#1 0x5555555554c7 in main harness.cpp:38
#2 0x7ffff6a2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#3 0x7ffff6a2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#4 0x555555555204 in _start (/home/mbereza/Projects/BioSig/repro_master/harness+0x1204) (BuildId: 71453a64ceb3bfa137fcadc13ca9ee9004d4faa3)
0x502000000228 is located 151 bytes after 1-byte region [0x502000000190,0x502000000191)
allocated by thread T0 here:
#0 0x7ffff78fd340 in calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:77
#1 0x7ffff72cbf53 in constructHDR /home/mbereza/Projects/BioSig/biosig-code/biosig4c++/biosig.c:1321
#2 0x555555555411 in main harness.cpp:35
#3 0x7ffff6a2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#4 0x7ffff6a2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#5 0x555555555204 in _start (/home/mbereza/Projects/BioSig/repro_master/harness+0x1204) (BuildId: 71453a64ceb3bfa137fcadc13ca9ee9004d4faa3)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/mbereza/Projects/BioSig/biosig-code/biosig4c++/biosig.c:9252 in sopen_extended
Shadow bytes around the buggy address:
0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x502000000000: fa fa 00 00 fa fa 00 fa fa fa 00 fa fa fa fd fa
0x502000000080: fa fa fd fd fa fa fd fd fa fa 00 00 fa fa 00 fa
0x502000000100: fa fa 04 fa fa fa fd fd fa fa 00 00 fa fa 00 fa
0x502000000180: fa fa 01 fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x502000000200: fa fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa fa
0x502000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
==49789==ABORTING
[Inferior 1 (process 49789) exited with code 01]
2025-08-06 - Vendor Disclosure
2025-08-24 - Vendor Patch Release
2025-08-25 - Public Release
Discovered by Mark Bereza and Lilith >_> of Cisco Talos.