The Biosig Project libbiosig MFER parsing multiple stack-based buffer overflow vulnerabilities
嗯,用户让我总结一篇文章的内容,控制在100字以内,而且不需要用“文章内容总结”之类的开头。看起来用户可能是一位研究人员或者开发者,需要快速了解漏洞的情况。 首先,文章提到了多个CVE编号,都是关于libbiosig 3.9.1版本的漏洞。这些漏洞都是栈溢出类型的,涉及MFER文件解析功能。攻击者可以通过构造恶意文件触发这些漏洞,导致任意代码执行。 接下来,我需要提取关键信息:漏洞类型、影响版本、攻击方式和CVSS评分。CVSSv3评分是9.8,说明风险很高。这些信息对于用户来说很重要,可能用于安全评估或修复。 然后,我得确保总结简洁明了,不超过100字。可能的结构是先点出漏洞数量和类型,再说明影响版本和攻击方式,最后提到CVSS评分。 最后检查一下有没有遗漏的重要信息,比如产品链接或发现者的信息可能不需要包含在内。重点放在漏洞本身和影响上。 </think> The Biosig Project libbiosig 3.9.1 contains multiple stack-based buffer overflow vulnerabilities in its MFER file parsing functionality, allowing arbitrary code execution via crafted MFER files. These vulnerabilities affect specific tags during file processing and were assigned high CVSSv3 scores of 9.8. 2025-12-11 00:0:58 Author: talosintelligence.com(查看原文) 阅读量:0 收藏

CVE-2025-66048,CVE-2025-66043,CVE-2025-66047,CVE-2025-66044,CVE-2025-66046,CVE-2025-66045

SUMMARY

Several stack-based buffer overflow vulnerabilities exists in the MFER parsing functionality of The Biosig Project libbiosig 3.9.1. A specially crafted MFER file can lead to arbitrary code execution. An attacker can provide a malicious file to trigger these vulnerabilities.

CONFIRMED VULNERABLE VERSIONS

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.1

PRODUCT URLS

libbiosig - https://biosig.sourceforge.net/index.html

CVSSv3 SCORE

9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-121 - Stack-based Buffer Overflow

DETAILS

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 structures, 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; these vulnerabilities concern the code path taken when reading from a file.

The file type used to exercise these vulnerabilities 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 a statically-sized 128-byte stack-allocated buffer named buf [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:

            uint8_t buf[128];                                                                                       // [1]
        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
        hdr->CHANNEL = (CHANNEL_TYPE*)realloc(hdr->CHANNEL, hdr->NS*sizeof(CHANNEL_TYPE));
        {
            CHANNEL_TYPE *hc = hdr->CHANNEL+0;
            hc->SPR = 0;
            hc->PhysDimCode = 4275;	// uV : default value in Table 5, ISO/FDIS 22077-1(E)ISO/WD 22077-1
            hc->Cal = 1.0;
            hc->Off = 0.0;
            hc->OnOff = 1;
            hc->LeadIdCode = 0;
            hc->GDFTYP = 3;
            hc->Transducer[0] = 0;
            hc->Label[0] = 0;
        }
        /* TAG */
        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 */
            curPos += ifread(&tmplen,1,1,hdr);
            char FlagInfiniteLength = 0;
            if ((tag==63) && (tmplen==0x80)) {                                                                      // [3]
                FlagInfiniteLength = -1; //Infinite Length
                len = 0;
            }
            else if (tmplen & 0x80) {                                                                               // [5]
                tmplen &= 0x7f;
                curPos += ifread(&buf,1,tmplen,hdr);
                len = 0;
                k = 0;
                while (k<tmplen)
                    len = (len<<8) + buf[k++];
            }
            else
                len = tmplen;                                                                                       // [4]

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. Of particular interest to these vulnerabiities is the Length portion, which the MFER specification allows to be encoded in one of three ways.

  1. To specify “indefinite data length,” the Length portion consists of a single octet whose value is 0x80, and the data is instead terminated via an “end-of-contents” frame where both the Tag and Data Length portions are single null octets. Libbiosig handles this case by reading the first octet of the Data Length portion (tmplen) and checking it against 0x80 [3].
  2. Beyond that, the length can be specified using a single octet. In this case, the most significant bit of the first (and only) Data Length octet must be 0, meaning this method can only be used for data lengths of at most 127 (0x7F) bytes. Libbiosig handles this case by simply setting the length (len) equal to the value of the first octet read (tmplen) [4].
  3. For lengths greater than 127 bytes, the length can be specified using multiple octets. In this case, the most significant bit of the first Data Length octet must be 1, and the remaining bits of that octet encode the number of subsequent octets used to encode the length. Libbiosig handles this case by reading a number of bytes into buf equal to the value of the first octet read (tmplen), interpreting this sequence of bytes as an integer, and storing the result in len [5].

Although libbiosig’s handling of the length encoding superficially adheres to the MFER specification, there are locations in the MFER parsing code that will attempt to copy this potentially huge length variable into a small, fixed-size buffer without ensuring that the destination buffer is large enough to hold len bytes.

CVE-2025-66043 - When Tag is 3

For this general analysis, the specific vulnerability exercised by the attached POC will be used, which occurs when the Tag is 3:

			else if (tag==3) {
				// character code
				char v[17];
				if (len>16) fprintf(stderr,"Warning MFER tag2 incorrect length %i>16\n",len);                       // [6]
				curPos += ifread(&v,1,len,hdr);                                                                     // [7]
				v[len]  = 0;
if (VERBOSE_LEVEL>7) fprintf(stdout,"MFER: character code <%s>\n",v);
			}

In this branch, a buffer named v is allocated on the stack with a fixed size of 17 bytes. Based on the warning message [6], it appears that Tag 3 is a special case where the length should never be greater than 16. Despite this, libbiosig will continue processing using whatever length was read from the input file, and will attempt to read that many bytes from the file into v via a call to ifread (which is simply a wrapper for fread) [7]. Although libbiosig will usually output an error message when the length for a particular frame doesn’t match what is expected based on its Tag, this length check does not impact the flow of execution and thus remains a legitimate attack vector.

As mentioned previously, v is a statically-sized 17-byte stack-allocated buffer. This becomes problematic in cases where len is greater than 17 bytes. In our testing, a maliciously crafted MFER file was supplied to libbiosig where the encoded length was 0x44 (68). This was confirmed via the error message [8] printed right before the ifread call that exercises the vulnerability:

pwndbg> n
Warning MFER tag2 incorrect length 68>16                                                                            // [8]
8775                                    curPos += ifread(&v,1,len,hdr);
────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────
In file: /home/mbereza/Projects/BioSig/biosig-3.9.1/biosig4c++/biosig.c:8775
8770                                 }
8771                         else if (tag==3) {
8772                                 // character code
8773                                 char v[17];
8774                                 if (len>16) fprintf(stderr,"Warning MFER tag2 incorrect length %i>16\n",len);
► 8775                                 curPos += ifread(&v,1,len,hdr);
8776                                 v[len]  = 0;
8777 if (VERBOSE_LEVEL>7) fprintf(stdout,"MFER: character code <%s>\n",v);
8778                         }
8779                         else if (tag==4) {
8780                                 // SPR
──────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────
► 0   0x7ffff733a31d sopen_extended+238605
1   0x5555555554c8 main+511
2   0x7ffff6a2a1ca __libc_start_call_main+122
3   0x7ffff6a2a28b __libc_start_main+139
4   0x555555555205 _start+37

From there, the program attempts to read that many bytes into v via a call to ifread [7]:

0x00007ffff72c6bdc in ifread (ptr=0x7ffff2f02160, size=1, nmemb=68, hdr=0x519000001980) at biosig.c:558
558             return(fread(ptr, size, nmemb, hdr->FILE.FID));
────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────
In file: /home/mbereza/Projects/BioSig/biosig-3.9.1/biosig4c++/biosig.c:558
553 #ifdef ZLIB_H
554         if (hdr->FILE.COMPRESSION>0)
555                 return(gzread(hdr->FILE.gzFID, ptr, size * nmemb)/size);
556         else
557 #endif
► 558         return(fread(ptr, size, nmemb, hdr->FILE.FID));
559 }
560 
561 size_t ifwrite(void* ptr, size_t size, size_t nmemb, HDRTYPE* hdr) {
562 #ifdef ZLIB_H
563         if (hdr->FILE.COMPRESSION)
──────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────
► 0   0x7ffff72c6bdc ifread+12
1   0x7ffff733a345 sopen_extended+238645
2   0x5555555554c8 main+511
3   0x7ffff6a2a1ca __libc_start_call_main+122
4   0x7ffff6a2a28b __libc_start_main+139
5   0x555555555205 _start+37

This results in a stack-based buffer overflow, as shown in the following AddressSanitizer output:

Run till exit from #0  0x00007ffff72c6bdc in ifread (ptr=0x7ffff2f02160, size=1, nmemb=68, hdr=0x519000001980) at biosig.c:558
=================================================================
==65283==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff2f02171 at pc 0x7ffff787ed7f bp 0x7fffffffa7e0 sp 0x7fffffff9f88
WRITE of size 68 at 0x7ffff2f02171 thread T0
	#0 0x7ffff787ed7e in fread ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:996
	#1 0x7ffff733a344 in sopen_extended /home/mbereza/Projects/BioSig/biosig-3.9.1/biosig4c++/biosig.c:8775
	#2 0x5555555554c7 in main harness.cpp:38
	#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_release/harness+0x1204) (BuildId: 72ee8fff2934eb1751765bd13e507472e6245218)

Address 0x7ffff2f02171 is located in stack of thread T0 at offset 8561 in frame
	#0 0x7ffff72fff1f in sopen_extended /home/mbereza/Projects/BioSig/biosig-3.9.1/biosig4c++/biosig.c:3720

This frame has 123 object(s):
	[32, 33) 'gdftyp' (line 8673)

	...

	[8544, 8561) 'v' (line 8773)
	[8608, 8628) 'tmp' (line 5323) <== Memory access at offset 8561 partially underflows this variable
	[8672, 8692) 'buf' (line 8189)

	...

	[10896, 11153) 'buf' (line 8947)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
	(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:996 in fread
Shadow bytes around the buggy address:
0x7ffff2f01e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ffff2f01f00: 00 00 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2
0x7ffff2f01f80: f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 02 f2 03 f2 03 f2
0x7ffff2f02000: 04 f2 04 f2 05 f2 f2 f2 05 f2 f2 f2 05 f2 f2 f2
0x7ffff2f02080: 06 f2 f2 f2 00 f2 f2 f2 00 01 f2 f2 00 01 f2 f2
=>0x7ffff2f02100: 00 01 f2 f2 00 00 01 f2 f2 f2 f2 f2 00 00[01]f2
0x7ffff2f02180: f2 f2 f2 f2 00 00 04 f2 f2 f2 f2 f2 00 00 04 f2
0x7ffff2f02200: f2 f2 f2 f2 00 00 00 00 00 f2 f2 f2 f2 f2 00 00
0x7ffff2f02280: 00 00 00 00 00 00 00 00 01 f2 f2 f2 f2 f2 00 00
0x7ffff2f02300: 00 00 00 00 00 00 00 00 01 f2 f2 f2 f2 f2 00 00
0x7ffff2f02380: 00 00 00 00 00 00 00 00 01 f2 f2 f2 f2 f2 00 00
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
==65283==ABORTING
[Inferior 1 (process 65283) exited with code 01]

Since the data written past the end of this buffer is read from hdr, which itself is populated using data from the input file, this data is attacker-controlled. The end result is a set of vulnerabilities where an attacker can write arbitrary data past the end of a stack-allocated buffer, potentially resulting in arbitrary code execution.

Crash Information

==65283==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff2f02171 at pc 0x7ffff787ed7f bp 0x7fffffffa7e0 sp 0x7fffffff9f88
WRITE of size 68 at 0x7ffff2f02171 thread T0
	#0 0x7ffff787ed7e in fread ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:996
	#1 0x7ffff733a344 in sopen_extended /home/mbereza/Projects/BioSig/biosig-3.9.1/biosig4c++/biosig.c:8775
	#2 0x5555555554c7 in main harness.cpp:38
	#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_release/harness+0x1204) (BuildId: 72ee8fff2934eb1751765bd13e507472e6245218)

Address 0x7ffff2f02171 is located in stack of thread T0 at offset 8561 in frame
	#0 0x7ffff72fff1f in sopen_extended /home/mbereza/Projects/BioSig/biosig-3.9.1/biosig4c++/biosig.c:3720

This frame has 123 object(s):
	[32, 33) 'gdftyp' (line 8673)

	...

	[8544, 8561) 'v' (line 8773)
	[8608, 8628) 'tmp' (line 5323) <== Memory access at offset 8561 partially underflows this variable
	[8672, 8692) 'buf' (line 8189)

	...

	[10896, 11153) 'buf' (line 8947)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
	(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:996 in fread
Shadow bytes around the buggy address:
0x7ffff2f01e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7ffff2f01f00: 00 00 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2
0x7ffff2f01f80: f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 02 f2 03 f2 03 f2
0x7ffff2f02000: 04 f2 04 f2 05 f2 f2 f2 05 f2 f2 f2 05 f2 f2 f2
0x7ffff2f02080: 06 f2 f2 f2 00 f2 f2 f2 00 01 f2 f2 00 01 f2 f2
=>0x7ffff2f02100: 00 01 f2 f2 00 00 01 f2 f2 f2 f2 f2 00 00[01]f2
0x7ffff2f02180: f2 f2 f2 f2 00 00 04 f2 f2 f2 f2 f2 00 00 04 f2
0x7ffff2f02200: f2 f2 f2 f2 00 00 00 00 00 f2 f2 f2 f2 f2 00 00
0x7ffff2f02280: 00 00 00 00 00 00 00 00 01 f2 f2 f2 f2 f2 00 00
0x7ffff2f02300: 00 00 00 00 00 00 00 00 01 f2 f2 f2 f2 f2 00 00
0x7ffff2f02380: 00 00 00 00 00 00 00 00 01 f2 f2 f2 f2 f2 00 00
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
==65283==ABORTING
[Inferior 1 (process 65283) exited with code 01]

CVE-2025-66044 - When Tag is 64

This vulnerability manifests on line 9151 of biosig.c, when the Tag is 64:

            else if (tag==64)     //0x40
            {
                // preamble
                char tmp[256];  // [1]
                curPos += ifread(tmp,1,len,hdr);

In this case, the overflowed buffer is the newly-declared tmp [1] instead of buf. While tmp is larger than buf, having a size of 256 bytes, a stack overflow can still occur in cases where len is encoded using multiple octets and is greater than 256.

CVE-2025-66045 - When Tag is 65

This vulnerability manifests on line 9162 of biosig.c, when the Tag is 65:

            else if (tag==65)     //0x41: patient event
            {
                // event table

                curPos += ifread(buf,1,len,hdr);

CVE-2025-66046 - When Tag is 67

This vulnerability manifests on line 9203 of biosig.c, when the Tag is 67:

            else if (tag==67)     //0x43: Sample skew
            {
                int skew=0;     // [1]
                curPos += ifread(&skew, 1, len,hdr);

In this case, the address of the newly-defined integer skew [1] is overflowed. This means a stack overflow can occur using much smaller values of len in this code path.

CVE-2025-66047 - When Tag is 131

This vulnerability manifests on line 9245 of biosig.c, when the Tag is 131:

            else if (tag==131)    //0x83
            {
                // Patient Age
                if (len!=7) fprintf(stderr,"Warning MFER tag131 incorrect length %i!=7\n",len);
                curPos += ifread(buf,1,len,hdr);

CVE-2025-66048 - When Tag is 133

This vulnerability manifests on line 9268 of biosig.c, when the Tag is 133:

            else if (tag==133)    //0x85
            {
                curPos += ifread(buf,1,len,hdr);
TIMELINE

2025-12-10 - Vendor Disclosure
2025-12-10 - Vendor Patch Release
2025-12-11 - Public Release

Discovered by Mark Bereza and Lilith >_> of Cisco Talos.


文章来源: https://talosintelligence.com/vulnerability_reports/TALOS-2025-2296
如有侵权请联系:admin#unsafe.sh