ZDI-CAN-28543: FontForge SFD File Parsing Heap-based Buffer Overflow Remote Code Execution Vulnerability
-- CVSS -----------------------------------------
8.8: AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
-- ABSTRACT -------------------------------------
Trend Micro's Zero Day Initiative has identified a vulnerability affecting the following products:
FontForge - FontForge
-- VULNERABILITY DETAILS ------------------------
* Version tested:aca4f524c6cb14cdc7bc4cd493492a33f5154797
* Platform tested:ubuntu 24.04
heap overflow vulnerability exists within the FontForge's SFDGetImage() function.
The overflow will be triggered when victim open the malicious sfd file with the fontforge application.
typedef struct clut {
int16_t clut_len;
unsigned int is_grey: 1;
uint32_t trans_index; /* will be ignored for cluts in images, use base->trans instead */
Color clut[256];
} GClut;
static ImageList *SFDGetImage(FILE *sfd) {
/* We've read the image token */
int width, height, image_type, bpl, clutlen, rlelen;
uint32_t trans;
struct _GImage *base;
GImage *image;
ImageList *img;
struct enc85 dec;
int i, ch;
memset(&dec,'\0', sizeof(dec)); dec.pos = -1;
dec.sfd = sfd;
getint(sfd,&width);
getint(sfd,&height);
getint(sfd,&image_type);
getint(sfd,&bpl);
getint(sfd,&clutlen); // input
gethex(sfd,&trans);
image = GImageCreate(image_type,width,height);
base = image->list_len==0?image->u.image:image->u.images[0];
img = calloc(1,sizeof(ImageList));
img->image = image;
getreal(sfd,&img->xoff);
getreal(sfd,&img->yoff);
getreal(sfd,&img->xscale);
getreal(sfd,&img->yscale);
while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
ungetc(ch,sfd);
rlelen = 0;
if ( isdigit(ch))
getint(sfd,&rlelen);
base->trans = trans;
if ( clutlen!=0 ) {
if ( base->clut==NULL )
base->clut = calloc(1,sizeof(GClut));
base->clut->clut_len = clutlen;
base->clut->trans_index = trans;
for ( i=0;i<clutlen; ++i ) {
int r,g,b;
r = Dec85(&dec);
g = Dec85(&dec);
b = Dec85(&dec);
base->clut->clut[i] = (r<<16)|(g<<8)|b; // heap overflow here, array size is 256
}
}
if ( rlelen!=0 ) {
rle2image(&dec,rlelen,base);
} else {
for ( i=0; i<height; ++i ) {
if ( image_type==it_rgba ) {
uint32_t *ipt = (uint32_t *) (base->data + i*base->bytes_per_line);
uint32_t *iend = (uint32_t *) (base->data + (i+1)*base->bytes_per_line);
int r,g,b, a;
while ( ipt<iend ) {
a = Dec85(&dec);
r = Dec85(&dec);
g = Dec85(&dec);
b = Dec85(&dec);
*ipt++ = (a<<24)|(r<<16)|(g<<8)|b;
}
} else if ( image_type==it_true ) {
int *ipt = (int *) (base->data + i*base->bytes_per_line);
int *iend = (int *) (base->data + (i+1)*base->bytes_per_line);
int r,g,b;
while ( ipt<iend ) {
r = Dec85(&dec);
g = Dec85(&dec);
b = Dec85(&dec);
*ipt++ = (r<<16)|(g<<8)|b;
}
} else {
uint8_t *pt = (uint8_t *) (base->data + i*base->bytes_per_line);
uint8_t *end = (uint8_t *) (base->data + (i+1)*base->bytes_per_line);
while ( pt<end ) {
*pt++ = Dec85(&dec);
}
}
}
}
img->bb.minx = img->xoff; img->bb.maxy = img->yoff;
img->bb.maxx = img->xoff + GImageGetWidth(img->image)*img->xscale;
img->bb.miny = img->yoff - GImageGetHeight(img->image)*img->yscale;
/* In old sfd files I failed to recognize bitmap pngs as bitmap, so put */
/* in a little check here that converts things which should be bitmap to */
/* bitmap */ /* Eventually it can be removed as all old sfd files get */
/* converted. 22/10/2002 */
if ( base->image_type==it_index && base->clut!=NULL && base->clut->clut_len==2 )
img->image = ImageAlterClut(img->image);
return( img );
}
ASAN report
=================================================================
==13332==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5190003e2588 at pc 0x76ee12ea180a bp 0x7ffd9f37f190 sp 0x7ffd9f37f180
WRITE of size 4 at 0x5190003e2588 thread T0
#0 0x76ee12ea1809 in SFDGetImage /home/wmliang/fontforge/fontforge/sfd.c:3683
#1 0x76ee12ec344e in SFDGetChar /home/wmliang/fontforge/fontforge/sfd.c:5654
#2 0x76ee12ed5559 in SFD_GetFont /home/wmliang/fontforge/fontforge/sfd.c:8983
#3 0x76ee12eda408 in SFD_Read /home/wmliang/fontforge/fontforge/sfd.c:9078
#4 0x76ee12f2d525 in _ReadSplineFont /home/wmliang/fontforge/fontforge/splinefont.c:1226
#5 0x76ee12f2ddf0 in LoadSplineFont /home/wmliang/fontforge/fontforge/splinefont.c:1420
#6 0x76ee12b8756f in ViewPostScriptFont /home/wmliang/fontforge/fontforge/fontviewbase.c:1386
#7 0x599a24a2ac9f in fontforge_main /home/wmliang/fontforge/fontforgeexe/startui.c:992
#8 0x76ee1002a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#9 0x76ee1002a28a in __libc_start_main_impl ../csu/libc-start.c:360
#10 0x599a2457c054 in _start (/home/wmliang/fontforge/build/bin/fontforge+0x180054) (BuildId: 45c09de68bbe576c3dd1339975fca7d8df397ad1)
0x5190003e2588 is located 0 bytes after 1032-byte region [0x5190003e2180,0x5190003e2588)
allocated by thread T0 here:
#0 0x76ee138fd340 in calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:77
#1 0x76ee12ea161d in SFDGetImage /home/wmliang/fontforge/fontforge/sfd.c:3675
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/wmliang/fontforge/fontforge/sfd.c:3683 in SFDGetImage
Shadow bytes around the buggy address:
0x5190003e2300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x5190003e2380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x5190003e2400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x5190003e2480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x5190003e2500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x5190003e2580: 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5190003e2600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5190003e2680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5190003e2700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5190003e2780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x5190003e2800: 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
==13332==ABORTING
-- CREDIT ---------------------------------------
This vulnerability was discovered by:
Anonymous working with Trend Micro Zero Day Initiative
-- FURTHER DETAILS ------------------------------
Supporting files:
If supporting files were contained with this report they are provided within a password protected ZIP file. The password is the ZDI candidate number in the form: ZDI-CAN-XXXX where XXXX is the ID number.
Please confirm receipt of this report. We expect all vendors to remediate ZDI vulnerabilities within 120 days of the reported date. If you are ready to release a patch at any point leading up to the deadline, please coordinate with us so that we may release our advisory detailing the issue. If the 120-day deadline is reached and no patch has been made available we will release a limited public advisory with our own mitigations, so that the public can protect themselves in the absence of a patch. Please keep us updated regarding the status of this issue and feel free to contact us at any time:
Zero Day Initiative
zdi-disclosures@trendmicro.com
The PGP key used for all ZDI vendor communications is available from:
http://www.zerodayinitiative.com/documents/disclosures-pgp-key.asc
-- INFORMATION ABOUT THE ZDI --------------------
Established by TippingPoint and acquired by Trend Micro, the Zero Day Initiative (ZDI) neither re-sells vulnerability details nor exploit code. Instead, upon notifying the affected product vendor, the ZDI provides its Trend Micro TippingPoint customers with zero day protection through its intrusion prevention technology. Explicit details regarding the specifics of the vulnerability are not exposed to any parties until an official vendor patch is publicly available.
Please contact us for further details or refer to:
http://www.zerodayinitiative.com
-- DISCLOSURE POLICY ----------------------------
Our vulnerability disclosure policy is available online at:
http://www.zerodayinitiative.com/advisories/disclosure_policy/