@@ -482,6 +482,11 @@ struct hwp3_docsummary_entry {
482482
483483#define HWP3_FIELD_LENGTH 512
484484
485+ /* Additional information block #1 payload prefixes. */
486+ #define HWP3_IMAGE_INFO_SIZE 32 /* 16-byte name + 16-byte image format. */
487+ #define HWP3_BACKGROUND_IMAGE_INFO_SIZE 324 /* Fixed background-image fields before the image bytes. */
488+ #define HWP3_OLE2_MIN_SIZE 8 /* Size of an OLE2 compound-file signature. */
489+
485490#define PI_PPFS 0 /* offset 0 (1 byte) - prior paragraph format style */
486491#define PI_NCHARS 1 /* offset 1 (2 bytes) - character count */
487492#define PI_NLINES 3 /* offset 3 (2 bytes) - line count */
@@ -1601,6 +1606,16 @@ static inline cl_error_t parsehwp3_infoblk_1(cli_ctx *ctx, fmap_t *dmap, size_t
16011606 case 1 : /* Image Data */
16021607 hwp3_debug ("HWP3.x: Information Block[%llu]: TYPE: Image Data\n" , infoloc );
16031608
1609+ /*
1610+ * The image bytes follow two fixed 16-byte fields: name and format.
1611+ * A shorter block cannot contain image data and subtracting the
1612+ * prefix size from its unsigned length would underflow.
1613+ */
1614+ if (infolen < HWP3_IMAGE_INFO_SIZE ) {
1615+ cli_errmsg ("HWP3.x: Information Block[%llu]: Image Data block is too short: %u\n" , infoloc , infolen );
1616+ return CL_EFORMAT ;
1617+ }
1618+
16041619 if (SCAN_COLLECT_METADATA )
16051620 cli_jsonstr (entry , "Type" , "Image Data" );
16061621
@@ -1619,10 +1634,9 @@ static inline cl_error_t parsehwp3_infoblk_1(cli_ctx *ctx, fmap_t *dmap, size_t
16191634 }
16201635 hwp3_debug ("HWP3.x: Information Block[%llu]: FORM: %s\n" , infoloc , field );
16211636#endif
1622- /* 32 bytes for extra data fields */
1623- if (infolen > 0 )
1624- ret = cli_magic_scan_nested_fmap_type (map , * offset + 32 , infolen - 32 , ctx ,
1625- CL_TYPE_ANY , NULL , LAYER_ATTRIBUTES_NONE );
1637+ /* Scan only the image bytes after the fixed metadata fields. */
1638+ ret = cli_magic_scan_nested_fmap_type (map , * offset + HWP3_IMAGE_INFO_SIZE , infolen - HWP3_IMAGE_INFO_SIZE , ctx ,
1639+ CL_TYPE_ANY , NULL , LAYER_ATTRIBUTES_NONE );
16261640 break ;
16271641 case 2 : /* OLE2 Data */
16281642 hwp3_debug ("HWP3.x: Information Block[%llu]: TYPE: OLE2 Data\n" , infoloc );
@@ -1682,9 +1696,19 @@ static inline cl_error_t parsehwp3_infoblk_1(cli_ctx *ctx, fmap_t *dmap, size_t
16821696 case 6 : /* Background Image Data */
16831697 hwp3_debug ("HWP3.x: Information Block[%llu]: TYPE: Background Image Data\n" , infoloc );
16841698
1699+ /*
1700+ * A background image has 324 bytes of fixed fields before its image.
1701+ * A shorter block cannot contain image data and subtracting the
1702+ * prefix size from its unsigned length would underflow.
1703+ */
1704+ if (infolen < HWP3_BACKGROUND_IMAGE_INFO_SIZE ) {
1705+ cli_errmsg ("HWP3.x: Information Block[%llu]: Background Image Data block is too short: %u\n" , infoloc , infolen );
1706+ return CL_EFORMAT ;
1707+ }
1708+
16851709 if (SCAN_COLLECT_METADATA ) {
16861710 cli_jsonstr (entry , "Type" , "Background Image Data" );
1687- cli_jsonint (entry , "ImageSize" , infolen - 324 );
1711+ cli_jsonint (entry , "ImageSize" , infolen - HWP3_BACKGROUND_IMAGE_INFO_SIZE );
16881712 }
16891713
16901714#if HWP3_DEBUG /* additional fields can be added */
@@ -1695,10 +1719,10 @@ static inline cl_error_t parsehwp3_infoblk_1(cli_ctx *ctx, fmap_t *dmap, size_t
16951719 }
16961720 hwp3_debug ("HWP3.x: Information Block[%llu]: NAME: %s\n" , infoloc , field );
16971721#endif
1698- /* 324 bytes for extra data fields */
1699- if ( infolen > 0 )
1700- ret = cli_magic_scan_nested_fmap_type ( map , * offset + 324 , infolen - 324 , ctx ,
1701- CL_TYPE_ANY , NULL , LAYER_ATTRIBUTES_NONE );
1722+ /* Scan only the image bytes after the fixed background-image fields. */
1723+ ret = cli_magic_scan_nested_fmap_type ( map , * offset + HWP3_BACKGROUND_IMAGE_INFO_SIZE ,
1724+ infolen - HWP3_BACKGROUND_IMAGE_INFO_SIZE , ctx ,
1725+ CL_TYPE_ANY , NULL , LAYER_ATTRIBUTES_NONE );
17021726 break ;
17031727 case 0x100 : /* Table Extension */
17041728 hwp3_debug ("HWP3.x: Information Block[%llu]: TYPE: Table Extension\n" , infoloc );
@@ -1727,12 +1751,97 @@ static inline cl_error_t parsehwp3_infoblk_1(cli_ctx *ctx, fmap_t *dmap, size_t
17271751 return ret ;
17281752}
17291753
1754+ static bool hwp3_infoblk_1_header_is_plausible (fmap_t * map , size_t offset )
1755+ {
1756+ uint32_t infoid , infolen ;
1757+
1758+ if (fmap_readn (map , & infoid , offset , sizeof (infoid )) != sizeof (infoid ))
1759+ return false;
1760+
1761+ infoid = le32_to_host (infoid );
1762+ /* Booking Information is the only block consisting solely of its 4-byte ID. */
1763+ if (infoid == 5 )
1764+ return true;
1765+
1766+ /* Recovery accepts only information block types defined by the HWP3 parser. */
1767+ if ((infoid > 6 ) && (infoid != 0x100 ) && (infoid != 0x101 ))
1768+ return false;
1769+
1770+ if (fmap_readn (map , & infolen , offset + sizeof (infoid ), sizeof (infolen )) != sizeof (infolen ))
1771+ return false;
1772+
1773+ infolen = le32_to_host (infolen );
1774+ if ((infoid == 0 ) && (infolen != 0 ))
1775+ return false;
1776+
1777+ return infolen <= map -> len - offset - sizeof (infoid ) - sizeof (infolen );
1778+ }
1779+
1780+ static bool findhwp3_infoblk_1 (fmap_t * map , size_t start , size_t * found )
1781+ {
1782+ uint32_t infoid , infolen ;
1783+ size_t offset , payload , next ;
1784+
1785+ if ((start >= map -> len ) || (map -> len - start < 12 ))
1786+ return false;
1787+
1788+ /*
1789+ * A paragraph error leaves its caller's offset at the start of the malformed
1790+ * paragraph, so the information-block boundary is no longer known. Search
1791+ * byte-by-byte because variable-length HWP3 paragraphs are not guaranteed to
1792+ * leave the following section on a fixed alignment.
1793+ */
1794+ for (offset = start ; offset <= map -> len - 12 ; offset ++ ) {
1795+ if (fmap_readn (map , & infoid , offset , sizeof (infoid )) != sizeof (infoid ))
1796+ return false;
1797+
1798+ infoid = le32_to_host (infoid );
1799+ if ((infoid != 1 ) && (infoid != 2 ) && (infoid != 6 ))
1800+ continue ;
1801+
1802+ if (fmap_readn (map , & infolen , offset + sizeof (infoid ), sizeof (infolen )) != sizeof (infolen ))
1803+ return false;
1804+
1805+ infolen = le32_to_host (infolen );
1806+ /*
1807+ * Limit resynchronization candidates to blocks that contain nested data.
1808+ * Their fixed prefixes also provide useful minimum lengths that reduce
1809+ * false matches while scanning malformed paragraph bytes.
1810+ */
1811+ if (((infoid == 1 ) && (infolen < HWP3_IMAGE_INFO_SIZE )) ||
1812+ ((infoid == 2 ) && (infolen < HWP3_OLE2_MIN_SIZE )) ||
1813+ ((infoid == 6 ) && (infolen < HWP3_BACKGROUND_IMAGE_INFO_SIZE )))
1814+ continue ;
1815+
1816+ payload = offset + sizeof (infoid ) + sizeof (infolen );
1817+ if (infolen > map -> len - payload )
1818+ continue ;
1819+
1820+ next = payload + infolen ;
1821+ /* Requiring a valid following header further avoids treating paragraph data as a block. */
1822+ if (!hwp3_infoblk_1_header_is_plausible (map , next ))
1823+ continue ;
1824+
1825+ * found = offset ;
1826+ return true;
1827+ }
1828+
1829+ return false;
1830+ }
1831+
1832+ static bool hwp3_parse_error_is_recoverable (cl_error_t ret )
1833+ {
1834+ /* Do not hide resource-limit, allocation, callback, or detection results. */
1835+ return (ret == CL_EREAD ) || (ret == CL_EFORMAT ) || (ret == CL_EPARSE );
1836+ }
1837+
17301838static cl_error_t hwp3_cb (void * cbdata , int fd , const char * filepath , cli_ctx * ctx )
17311839{
17321840 cl_error_t ret = CL_SUCCESS ;
17331841 fmap_t * map , * dmap ;
17341842 size_t offset , start , new_offset ;
17351843 int i , p = 0 , last = 0 ;
1844+ bool paragraph_parse_failed = false;
17361845 uint16_t nstyles ;
17371846 json_object * fonts = NULL ;
17381847
@@ -1820,9 +1929,20 @@ static cl_error_t hwp3_cb(void *cbdata, int fd, const char *filepath, cli_ctx *c
18201929 while (!last && ((ret = parsehwp3_paragraph (ctx , map , p ++ , 0 , & offset , & last )) == CL_SUCCESS )) continue ;
18211930 /* return is never a virus */
18221931 if (ret != CL_SUCCESS ) {
1823- if (dmap )
1824- fmap_free (dmap );
1825- return ret ;
1932+ if (!hwp3_parse_error_is_recoverable (ret )) {
1933+ if (dmap )
1934+ fmap_free (dmap );
1935+ return ret ;
1936+ }
1937+
1938+ paragraph_parse_failed = true;
1939+ cli_warnmsg ("HWP3.x: Paragraph parsing failed; attempting to recover additional information blocks\n" );
1940+
1941+ if (!findhwp3_infoblk_1 (map , offset , & offset )) {
1942+ cli_warnmsg ("HWP3.x: Failed to recover additional information blocks; scanning the complete content stream\n" );
1943+ offset = map -> len ;
1944+ }
1945+ ret = CL_SUCCESS ;
18261946 }
18271947
18281948 if (SCAN_COLLECT_METADATA )
@@ -1832,9 +1952,12 @@ static cl_error_t hwp3_cb(void *cbdata, int fd, const char *filepath, cli_ctx *c
18321952 /* 'additional information block #1's - attachments and media */
18331953 while (!last && ((ret = parsehwp3_infoblk_1 (ctx , map , & offset , & last )) == CL_SUCCESS )) continue ;
18341954
1835- /* scan the uncompressed stream - both compressed and uncompressed cases [ALLMATCH] */
1836- if (ret == CL_SUCCESS ) {
1837- size_t dlen = offset - start ;
1955+ /*
1956+ * Scan the complete content stream after paragraph recovery. This preserves
1957+ * raw-signature coverage even if a later information block is also malformed.
1958+ */
1959+ if ((ret == CL_SUCCESS ) || (paragraph_parse_failed && hwp3_parse_error_is_recoverable (ret ))) {
1960+ size_t dlen = paragraph_parse_failed ? map -> len - start : offset - start ;
18381961
18391962 ret = cli_magic_scan_nested_fmap_type (map , start , dlen , ctx , CL_TYPE_ANY , NULL , LAYER_ATTRIBUTES_NONE );
18401963 }
0 commit comments