/src/clamav/libclamav/dmg.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2013-2023 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
3 | | * Copyright (C) 2013 Sourcefire, Inc. |
4 | | * |
5 | | * Authors: David Raynor <draynor@sourcefire.com> |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License version 2 as |
9 | | * published by the Free Software Foundation. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
19 | | * MA 02110-1301, USA. |
20 | | */ |
21 | | |
22 | | #if HAVE_CONFIG_H |
23 | | #include "clamav-config.h" |
24 | | #endif |
25 | | |
26 | | #include <stdio.h> |
27 | | #include <errno.h> |
28 | | #if HAVE_STRING_H |
29 | | #include <string.h> |
30 | | #endif |
31 | | #include <ctype.h> |
32 | | #include <fcntl.h> |
33 | | #if HAVE_SYS_PARAM_H |
34 | | #include <sys/param.h> /* for NAME_MAX */ |
35 | | #endif |
36 | | |
37 | | #if HAVE_LIBZ |
38 | | #include <zlib.h> |
39 | | #endif |
40 | | #if HAVE_BZLIB_H |
41 | | #include <bzlib.h> |
42 | | #ifdef NOBZ2PREFIX |
43 | | #define BZ2_bzDecompress bzDecompress |
44 | | #define BZ2_bzDecompressEnd bzDecompressEnd |
45 | | #define BZ2_bzDecompressInit bzDecompressInit |
46 | | #endif |
47 | | #endif |
48 | | |
49 | | #if HAVE_LIBXML2 |
50 | | #include <libxml/xmlreader.h> |
51 | | #endif |
52 | | |
53 | | #include "clamav.h" |
54 | | #include "others.h" |
55 | | #include "dmg.h" |
56 | | #include "scanners.h" |
57 | | #include "sf_base64decode.h" |
58 | | #include "adc.h" |
59 | | |
60 | | /* #define DEBUG_DMG_PARSE */ |
61 | | /* #define DEBUG_DMG_BZIP */ |
62 | | |
63 | | #ifdef DEBUG_DMG_PARSE |
64 | | #define dmg_parsemsg(...) cli_dbgmsg(__VA_ARGS__) |
65 | | #else |
66 | 4.27k | #define dmg_parsemsg(...) ; |
67 | | #endif |
68 | | |
69 | | #ifdef DEBUG_DMG_BZIP |
70 | | #define dmg_bzipmsg(...) cli_dbgmsg(__VA_ARGS__) |
71 | | #else |
72 | 0 | #define dmg_bzipmsg(...) ; |
73 | | #endif |
74 | | |
75 | | enum dmgReadState { |
76 | | DMG_FIND_BASE_PLIST = 0, |
77 | | DMG_FIND_BASE_DICT = 1, |
78 | | DMG_FIND_KEY_RESOURCE_FORK = 2, |
79 | | DMG_FIND_DICT_RESOURCE_FORK = 3, |
80 | | DMG_FIND_KEY_BLKX = 4, |
81 | | DMG_FIND_BLKX_CONTAINER = 5, |
82 | | DMG_FIND_KEY_DATA = 6, |
83 | | DMG_FIND_DATA_MISH = 7, |
84 | | DMG_MAX_STATE = 8 |
85 | | }; |
86 | | |
87 | | static int dmg_extract_xml(cli_ctx *, char *, struct dmg_koly_block *); |
88 | | #if HAVE_LIBXML2 |
89 | | static int dmg_decode_mish(cli_ctx *, unsigned int *, xmlChar *, struct dmg_mish_with_stripes *); |
90 | | #endif |
91 | | static int cmp_mish_stripes(const void *stripe_a, const void *stripe_b); |
92 | | static int dmg_track_sectors(uint64_t *, uint8_t *, uint32_t, uint32_t, uint64_t); |
93 | | static int dmg_handle_mish(cli_ctx *, unsigned int, char *, uint64_t, struct dmg_mish_with_stripes *); |
94 | | |
95 | | int cli_scandmg(cli_ctx *ctx) |
96 | 4.21k | { |
97 | 4.21k | struct dmg_koly_block hdr; |
98 | 4.21k | int ret; |
99 | 4.21k | size_t maplen, nread; |
100 | 4.21k | size_t pos = 0; |
101 | 4.21k | char *dirname; |
102 | 4.21k | const char *outdata; |
103 | 4.21k | unsigned int file = 0; |
104 | 4.21k | struct dmg_mish_with_stripes *mish_list = NULL, *mish_list_tail = NULL; |
105 | 4.21k | enum dmgReadState state = DMG_FIND_BASE_PLIST; |
106 | 4.21k | int stateDepth[DMG_MAX_STATE]; |
107 | 4.21k | #if HAVE_LIBXML2 |
108 | 4.21k | xmlTextReaderPtr reader; |
109 | 4.21k | #endif |
110 | | |
111 | 4.21k | if (!ctx || !ctx->fmap) { |
112 | 0 | cli_errmsg("cli_scandmg: Invalid context\n"); |
113 | 0 | return CL_ENULLARG; |
114 | 0 | } |
115 | | |
116 | 4.21k | maplen = ctx->fmap->len; |
117 | 4.21k | if (maplen <= 512) { |
118 | 0 | cli_dbgmsg("cli_scandmg: DMG smaller than DMG koly block!\n"); |
119 | 0 | return CL_CLEAN; |
120 | 0 | } |
121 | 4.21k | pos = maplen - 512; |
122 | | |
123 | | /* Grab koly block */ |
124 | 4.21k | if (fmap_readn(ctx->fmap, &hdr, pos, sizeof(hdr)) != sizeof(hdr)) { |
125 | 0 | cli_dbgmsg("cli_scandmg: Invalid DMG trailer block\n"); |
126 | 0 | return CL_EFORMAT; |
127 | 0 | } |
128 | | |
129 | | /* Check magic */ |
130 | 4.21k | hdr.magic = be32_to_host(hdr.magic); |
131 | 4.21k | if (hdr.magic == 0x6b6f6c79) { |
132 | 4.21k | cli_dbgmsg("cli_scandmg: Found koly block @ %zu\n", pos); |
133 | 4.21k | } else { |
134 | 0 | cli_dbgmsg("cli_scandmg: No koly magic, %8x\n", hdr.magic); |
135 | 0 | return CL_EFORMAT; |
136 | 0 | } |
137 | | |
138 | 4.21k | hdr.dataForkOffset = be64_to_host(hdr.dataForkOffset); |
139 | 4.21k | hdr.dataForkLength = be64_to_host(hdr.dataForkLength); |
140 | 4.21k | cli_dbgmsg("cli_scandmg: data offset %lu len %d\n", (unsigned long)hdr.dataForkOffset, (int)hdr.dataForkLength); |
141 | | |
142 | 4.21k | hdr.xmlOffset = be64_to_host(hdr.xmlOffset); |
143 | 4.21k | hdr.xmlLength = be64_to_host(hdr.xmlLength); |
144 | 4.21k | if (hdr.xmlLength > (uint64_t)INT_MAX) { |
145 | 300 | cli_dbgmsg("cli_scandmg: The embedded XML is way larger than necessary, and probably corrupt or tampered with.\n"); |
146 | 300 | return CL_EFORMAT; |
147 | 300 | } |
148 | 3.91k | if ((hdr.xmlOffset > (uint64_t)maplen) || (hdr.xmlLength > (uint64_t)maplen) || (hdr.xmlOffset + hdr.xmlLength) > (uint64_t)maplen) { |
149 | 3.85k | cli_dbgmsg("cli_scandmg: XML out of range for this file\n"); |
150 | 3.85k | return CL_EFORMAT; |
151 | 3.85k | } |
152 | 65 | cli_dbgmsg("cli_scandmg: XML offset %lu len %d\n", (unsigned long)hdr.xmlOffset, (int)hdr.xmlLength); |
153 | 65 | if (hdr.xmlLength == 0) { |
154 | 7 | cli_dbgmsg("cli_scandmg: Embedded XML length is zero.\n"); |
155 | 7 | return CL_EFORMAT; |
156 | 7 | } |
157 | | |
158 | | /* Create temp folder for contents */ |
159 | 58 | if (!(dirname = cli_gentemp_with_prefix(ctx->sub_tmpdir, "dmg-tmp"))) { |
160 | 0 | return CL_ETMPDIR; |
161 | 0 | } |
162 | 58 | if (mkdir(dirname, 0700)) { |
163 | 0 | cli_errmsg("cli_scandmg: Cannot create temporary directory %s\n", dirname); |
164 | 0 | free(dirname); |
165 | 0 | return CL_ETMPDIR; |
166 | 0 | } |
167 | 58 | cli_dbgmsg("cli_scandmg: Extracting into %s\n", dirname); |
168 | | |
169 | | /* Dump XML to tempfile, if needed */ |
170 | 58 | if (ctx->engine->keeptmp && !(ctx->engine->engine_options & ENGINE_OPTIONS_FORCE_TO_DISK)) { |
171 | 0 | int xret; |
172 | 0 | xret = dmg_extract_xml(ctx, dirname, &hdr); |
173 | |
|
174 | 0 | if (xret != CL_SUCCESS) { |
175 | | /* Printed err detail inside dmg_extract_xml */ |
176 | 0 | free(dirname); |
177 | 0 | return xret; |
178 | 0 | } |
179 | 0 | } |
180 | | |
181 | | /* scan XML with cli_magic_scan_nested_fmap_type */ |
182 | 58 | ret = cli_magic_scan_nested_fmap_type(ctx->fmap, (size_t)hdr.xmlOffset, (size_t)hdr.xmlLength, |
183 | 58 | ctx, CL_TYPE_ANY, NULL, LAYER_ATTRIBUTES_NONE); |
184 | 58 | if (ret != CL_CLEAN) { |
185 | 1 | cli_dbgmsg("cli_scandmg: retcode from scanning TOC xml: %s\n", cl_strerror(ret)); |
186 | 1 | if (!ctx->engine->keeptmp) |
187 | 1 | cli_rmdirs(dirname); |
188 | 1 | free(dirname); |
189 | 1 | return ret; |
190 | 1 | } |
191 | | |
192 | | /* page data from map */ |
193 | 57 | outdata = fmap_need_off_once_len(ctx->fmap, hdr.xmlOffset, hdr.xmlLength, &nread); |
194 | 57 | if (!outdata || (nread != hdr.xmlLength)) { |
195 | 0 | cli_errmsg("cli_scandmg: Failed getting XML from map, len %d\n", (int)hdr.xmlLength); |
196 | 0 | if (!ctx->engine->keeptmp) |
197 | 0 | cli_rmdirs(dirname); |
198 | 0 | free(dirname); |
199 | 0 | return CL_EMAP; |
200 | 0 | } |
201 | | |
202 | | /* time to walk the tree */ |
203 | | /* plist -> dict -> (key:resource_fork) dict -> (key:blkx) array -> dict */ |
204 | | /* each of those bottom level dict should have 4 parts */ |
205 | | /* [ Attributes, Data, ID, Name ], where Data is Base64 mish block */ |
206 | | |
207 | | /* This is the block where we require libxml2 */ |
208 | 57 | #if HAVE_LIBXML2 |
209 | | |
210 | 57 | #define DMG_XML_PARSE_OPTS ((XML_PARSE_NONET | XML_PARSE_COMPACT) | CLAMAV_MIN_XMLREADER_FLAGS) |
211 | | |
212 | 57 | reader = xmlReaderForMemory(outdata, (int)hdr.xmlLength, "toc.xml", NULL, DMG_XML_PARSE_OPTS); |
213 | 57 | if (!reader) { |
214 | 0 | cli_dbgmsg("cli_scandmg: Failed parsing XML!\n"); |
215 | 0 | if (!ctx->engine->keeptmp) |
216 | 0 | cli_rmdirs(dirname); |
217 | 0 | free(dirname); |
218 | 0 | return CL_EFORMAT; |
219 | 0 | } |
220 | | |
221 | 57 | stateDepth[DMG_FIND_BASE_PLIST] = -1; |
222 | | |
223 | | // May need to check for (xmlTextReaderIsEmptyElement(reader) == 0) |
224 | | |
225 | | /* Break loop if have return code or reader can't read any more */ |
226 | 5.07k | while ((ret == CL_CLEAN) && (xmlTextReaderRead(reader) == 1)) { |
227 | 5.02k | xmlReaderTypes nodeType; |
228 | 5.02k | nodeType = xmlTextReaderNodeType(reader); |
229 | | |
230 | 5.02k | if (nodeType == XML_READER_TYPE_ELEMENT) { |
231 | | // New element, do name check |
232 | 1.50k | xmlChar *nodeName; |
233 | 1.50k | int depth; |
234 | | |
235 | 1.50k | depth = xmlTextReaderDepth(reader); |
236 | 1.50k | if (depth < 0) { |
237 | 0 | break; |
238 | 0 | } |
239 | 1.50k | if ((depth > 50) && SCAN_HEURISTICS) { |
240 | | // Possible heuristic, should limit runaway |
241 | 0 | cli_dbgmsg("cli_scandmg: Excessive nesting in DMG TOC.\n"); |
242 | 0 | break; |
243 | 0 | } |
244 | 1.50k | nodeName = xmlTextReaderLocalName(reader); |
245 | 1.50k | if (!nodeName) |
246 | 0 | continue; |
247 | 1.50k | dmg_parsemsg("read: name %s depth %d\n", nodeName, depth); |
248 | | |
249 | 1.50k | if ((state == DMG_FIND_DATA_MISH) && (depth == stateDepth[state - 1])) { |
250 | 91 | xmlChar *textValue; |
251 | 91 | struct dmg_mish_with_stripes *mish_set; |
252 | | /* Reset state early, for continue cases */ |
253 | 91 | stateDepth[DMG_FIND_KEY_DATA] = -1; |
254 | 91 | state--; |
255 | 91 | if (xmlStrcmp(nodeName, (const xmlChar *)"data") != 0) { |
256 | 1 | cli_dbgmsg("cli_scandmg: Not blkx data element\n"); |
257 | 1 | xmlFree(nodeName); |
258 | 1 | continue; |
259 | 1 | } |
260 | 90 | dmg_parsemsg("read: Found blkx data element\n"); |
261 | | /* Pull out data content from text */ |
262 | 90 | if (xmlTextReaderIsEmptyElement(reader)) { |
263 | 0 | cli_dbgmsg("cli_scandmg: blkx data element is empty\n"); |
264 | 0 | xmlFree(nodeName); |
265 | 0 | continue; |
266 | 0 | } |
267 | 90 | if (xmlTextReaderRead(reader) != 1) { |
268 | 5 | xmlFree(nodeName); |
269 | 5 | break; |
270 | 5 | } |
271 | 85 | if (xmlTextReaderNodeType(reader) != XML_READER_TYPE_TEXT) { |
272 | 0 | cli_dbgmsg("cli_scandmg: Next node not text\n"); |
273 | 0 | xmlFree(nodeName); |
274 | 0 | continue; |
275 | 0 | } |
276 | 85 | textValue = xmlTextReaderValue(reader); |
277 | 85 | if (textValue == NULL) { |
278 | 0 | xmlFree(nodeName); |
279 | 0 | continue; |
280 | 0 | } |
281 | | /* Have encoded mish block */ |
282 | 85 | mish_set = cli_malloc(sizeof(struct dmg_mish_with_stripes)); |
283 | 85 | if (mish_set == NULL) { |
284 | 0 | ret = CL_EMEM; |
285 | 0 | xmlFree(textValue); |
286 | 0 | xmlFree(nodeName); |
287 | 0 | break; |
288 | 0 | } |
289 | 85 | ret = dmg_decode_mish(ctx, &file, textValue, mish_set); |
290 | 85 | xmlFree(textValue); |
291 | 85 | if (ret == CL_EFORMAT) { |
292 | | /* Didn't decode, or not a mish block */ |
293 | 35 | ret = CL_CLEAN; |
294 | 35 | free(mish_set); |
295 | 35 | xmlFree(nodeName); |
296 | 35 | continue; |
297 | 50 | } else if (ret != CL_CLEAN) { |
298 | 0 | xmlFree(nodeName); |
299 | 0 | free(mish_set); |
300 | 0 | continue; |
301 | 0 | } |
302 | | /* Add mish block to list */ |
303 | 50 | if (mish_list_tail != NULL) { |
304 | 30 | mish_list_tail->next = mish_set; |
305 | 30 | mish_list_tail = mish_set; |
306 | 30 | } else { |
307 | 20 | mish_list = mish_set; |
308 | 20 | mish_list_tail = mish_set; |
309 | 20 | } |
310 | 50 | mish_list_tail->next = NULL; |
311 | 50 | } |
312 | 1.46k | if ((state == DMG_FIND_KEY_DATA) && (depth > stateDepth[state - 1]) && (xmlStrcmp(nodeName, (const xmlChar *)"key") == 0)) { |
313 | 468 | xmlChar *textValue; |
314 | 468 | dmg_parsemsg("read: Found key - checking for Data\n"); |
315 | 468 | if (xmlTextReaderRead(reader) != 1) { |
316 | 1 | xmlFree(nodeName); |
317 | 1 | break; |
318 | 1 | } |
319 | 467 | if (xmlTextReaderNodeType(reader) != XML_READER_TYPE_TEXT) { |
320 | 0 | cli_dbgmsg("cli_scandmg: Key node no text\n"); |
321 | 0 | xmlFree(nodeName); |
322 | 0 | continue; |
323 | 0 | } |
324 | 467 | textValue = xmlTextReaderValue(reader); |
325 | 467 | if (textValue == NULL) { |
326 | 0 | cli_dbgmsg("cli_scandmg: no value from xmlTextReaderValue\n"); |
327 | 0 | xmlFree(nodeName); |
328 | 0 | continue; |
329 | 0 | } |
330 | 467 | if (xmlStrcmp(textValue, (const xmlChar *)"Data") == 0) { |
331 | 92 | dmg_parsemsg("read: Matched data\n"); |
332 | 92 | stateDepth[DMG_FIND_KEY_DATA] = depth; |
333 | 92 | state++; |
334 | 375 | } else { |
335 | 375 | dmg_parsemsg("read: text value is %s\n", textValue); |
336 | 375 | } |
337 | 467 | xmlFree(textValue); |
338 | 467 | } |
339 | 1.45k | if ((state == DMG_FIND_BLKX_CONTAINER) && (depth == stateDepth[state - 1])) { |
340 | 26 | if (xmlStrcmp(nodeName, (const xmlChar *)"array") == 0) { |
341 | 26 | dmg_parsemsg("read: Found array blkx\n"); |
342 | 26 | stateDepth[DMG_FIND_BLKX_CONTAINER] = depth; |
343 | 26 | state++; |
344 | 26 | } else if (xmlStrcmp(nodeName, (const xmlChar *)"dict") == 0) { |
345 | 0 | dmg_parsemsg("read: Found dict blkx\n"); |
346 | 0 | stateDepth[DMG_FIND_BLKX_CONTAINER] = depth; |
347 | 0 | state++; |
348 | 0 | } else { |
349 | 0 | cli_dbgmsg("cli_scandmg: Bad blkx, not container\n"); |
350 | 0 | stateDepth[DMG_FIND_KEY_BLKX] = -1; |
351 | 0 | state--; |
352 | 0 | } |
353 | 26 | } |
354 | 1.45k | if ((state == DMG_FIND_KEY_BLKX) && (depth == stateDepth[state - 1] + 1) && (xmlStrcmp(nodeName, (const xmlChar *)"key") == 0)) { |
355 | 40 | xmlChar *textValue; |
356 | 40 | dmg_parsemsg("read: Found key - checking for blkx\n"); |
357 | 40 | if (xmlTextReaderRead(reader) != 1) { |
358 | 0 | xmlFree(nodeName); |
359 | 0 | break; |
360 | 0 | } |
361 | 40 | if (xmlTextReaderNodeType(reader) != XML_READER_TYPE_TEXT) { |
362 | 0 | cli_dbgmsg("cli_scandmg: Key node no text\n"); |
363 | 0 | xmlFree(nodeName); |
364 | 0 | continue; |
365 | 0 | } |
366 | 40 | textValue = xmlTextReaderValue(reader); |
367 | 40 | if (textValue == NULL) { |
368 | 0 | cli_dbgmsg("cli_scandmg: no value from xmlTextReaderValue\n"); |
369 | 0 | xmlFree(nodeName); |
370 | 0 | continue; |
371 | 0 | } |
372 | 40 | if (xmlStrcmp(textValue, (const xmlChar *)"blkx") == 0) { |
373 | 26 | cli_dbgmsg("cli_scandmg: Matched blkx\n"); |
374 | 26 | stateDepth[DMG_FIND_KEY_BLKX] = depth; |
375 | 26 | state++; |
376 | 26 | } else { |
377 | 14 | cli_dbgmsg("cli_scandmg: wanted blkx, text value is %s\n", textValue); |
378 | 14 | } |
379 | 40 | xmlFree(textValue); |
380 | 40 | } |
381 | 1.45k | if ((state == DMG_FIND_DICT_RESOURCE_FORK) && (depth == stateDepth[state - 1])) { |
382 | 31 | if (xmlStrcmp(nodeName, (const xmlChar *)"dict") == 0) { |
383 | 27 | dmg_parsemsg("read: Found resource-fork dict\n"); |
384 | 27 | stateDepth[DMG_FIND_DICT_RESOURCE_FORK] = depth; |
385 | 27 | state++; |
386 | 27 | } else { |
387 | 4 | dmg_parsemsg("read: Not resource-fork dict\n"); |
388 | 4 | stateDepth[DMG_FIND_KEY_RESOURCE_FORK] = -1; |
389 | 4 | state--; |
390 | 4 | } |
391 | 31 | } |
392 | 1.45k | if ((state == DMG_FIND_KEY_RESOURCE_FORK) && (depth == stateDepth[state - 1] + 1) && (xmlStrcmp(nodeName, (const xmlChar *)"key") == 0)) { |
393 | 32 | dmg_parsemsg("read: Found resource-fork key\n"); |
394 | 32 | stateDepth[DMG_FIND_KEY_RESOURCE_FORK] = depth; |
395 | 32 | state++; |
396 | 32 | } |
397 | 1.45k | if ((state == DMG_FIND_BASE_DICT) && (depth == stateDepth[state - 1] + 1) && (xmlStrcmp(nodeName, (const xmlChar *)"dict") == 0)) { |
398 | 31 | dmg_parsemsg("read: Found dict start\n"); |
399 | 31 | stateDepth[DMG_FIND_BASE_DICT] = depth; |
400 | 31 | state++; |
401 | 31 | } |
402 | 1.45k | if ((state == DMG_FIND_BASE_PLIST) && (xmlStrcmp(nodeName, (const xmlChar *)"plist") == 0)) { |
403 | 34 | dmg_parsemsg("read: Found plist start\n"); |
404 | 34 | stateDepth[DMG_FIND_BASE_PLIST] = depth; |
405 | 34 | state++; |
406 | 34 | } |
407 | 1.45k | xmlFree(nodeName); |
408 | 3.52k | } else if ((nodeType == XML_READER_TYPE_END_ELEMENT) && (state > DMG_FIND_BASE_PLIST)) { |
409 | 1.30k | int significantEnd = 0; |
410 | 1.30k | int depth = xmlTextReaderDepth(reader); |
411 | 1.30k | if (depth < 0) { |
412 | 0 | break; |
413 | 1.30k | } else if (depth < stateDepth[state - 1]) { |
414 | 4 | significantEnd = 1; |
415 | 1.30k | } else if ((depth == stateDepth[state - 1]) && (state - 1 == DMG_FIND_BLKX_CONTAINER)) { |
416 | | /* Special case, ending blkx container */ |
417 | 13 | significantEnd = 1; |
418 | 13 | } |
419 | 1.30k | if (significantEnd) { |
420 | 17 | dmg_parsemsg("read: significant end tag, state %d\n", state); |
421 | 17 | stateDepth[state - 1] = -1; |
422 | 17 | state--; |
423 | 17 | if ((state - 1 == DMG_FIND_KEY_RESOURCE_FORK) || (state - 1 == DMG_FIND_KEY_BLKX)) { |
424 | | /* Keys end their own tag (validly) and the next state depends on the following tag */ |
425 | | // cli_dbgmsg("read: significant end tag ending prior key state\n"); |
426 | 15 | stateDepth[state - 1] = -1; |
427 | 15 | state--; |
428 | 15 | } |
429 | 1.29k | } else { |
430 | 1.29k | dmg_parsemsg("read: not significant end tag, state %d depth %d prior depth %d\n", state, depth, stateDepth[state - 1]); |
431 | 1.29k | } |
432 | 1.30k | } |
433 | 5.02k | } |
434 | | |
435 | 57 | xmlFreeTextReader(reader); |
436 | | |
437 | | #else |
438 | | |
439 | | cli_dbgmsg("cli_scandmg: libxml2 support is compiled out. It is required for full DMG support.\n"); |
440 | | |
441 | | #endif |
442 | | |
443 | | /* Loop over mish array */ |
444 | 57 | file = 0; |
445 | 93 | while ((ret == CL_CLEAN) && (mish_list != NULL)) { |
446 | | /* Handle & scan mish block */ |
447 | 36 | ret = dmg_handle_mish(ctx, file++, dirname, hdr.xmlOffset, mish_list); |
448 | 36 | free(mish_list->mish); |
449 | 36 | mish_list_tail = mish_list; |
450 | 36 | mish_list = mish_list->next; |
451 | 36 | free(mish_list_tail); |
452 | 36 | } |
453 | | |
454 | | /* Cleanup */ |
455 | | /* If error occurred, need to free mish items and mish blocks */ |
456 | 71 | while (mish_list != NULL) { |
457 | 14 | free(mish_list->mish); |
458 | 14 | mish_list_tail = mish_list; |
459 | 14 | mish_list = mish_list->next; |
460 | 14 | free(mish_list_tail); |
461 | 14 | } |
462 | 57 | if (!ctx->engine->keeptmp) |
463 | 57 | cli_rmdirs(dirname); |
464 | 57 | free(dirname); |
465 | 57 | return ret; |
466 | 57 | } |
467 | | |
468 | | #if HAVE_LIBXML2 |
469 | | /* Transform the base64-encoded string into the binary structure |
470 | | * After this, the base64 string (from xml) can be released |
471 | | * If mish_set->mish is set by this function, it must be freed by the caller */ |
472 | | static int dmg_decode_mish(cli_ctx *ctx, unsigned int *mishblocknum, xmlChar *mish_base64, |
473 | | struct dmg_mish_with_stripes *mish_set) |
474 | 85 | { |
475 | 85 | size_t base64_len, buff_size, decoded_len; |
476 | 85 | uint8_t *decoded; |
477 | 85 | const uint8_t mish_magic[4] = {0x6d, 0x69, 0x73, 0x68}; |
478 | | |
479 | 85 | UNUSEDPARAM(ctx); |
480 | | |
481 | 85 | (*mishblocknum)++; |
482 | 85 | base64_len = strlen((const char *)mish_base64); |
483 | 85 | dmg_parsemsg("dmg_decode_mish: len of encoded block %u is %lu\n", *mishblocknum, base64_len); |
484 | | |
485 | | /* speed vs memory, could walk the encoded data and skip whitespace in calculation */ |
486 | 85 | buff_size = 3 * base64_len / 4 + 4; |
487 | 85 | dmg_parsemsg("dmg_decode_mish: buffer for mish block %u is %lu\n", *mishblocknum, (unsigned long)buff_size); |
488 | 85 | decoded = cli_malloc(buff_size); |
489 | 85 | if (!decoded) |
490 | 0 | return CL_EMEM; |
491 | | |
492 | 85 | if (sf_base64decode((uint8_t *)mish_base64, base64_len, decoded, buff_size - 1, &decoded_len)) { |
493 | 8 | cli_dbgmsg("dmg_decode_mish: failed base64 decoding on mish block %u\n", *mishblocknum); |
494 | 8 | free(decoded); |
495 | 8 | return CL_EFORMAT; |
496 | 8 | } |
497 | 77 | dmg_parsemsg("dmg_decode_mish: len of decoded mish block %u is %lu\n", *mishblocknum, (unsigned long)decoded_len); |
498 | | |
499 | 77 | if (decoded_len < sizeof(struct dmg_mish_block)) { |
500 | 5 | cli_dbgmsg("dmg_decode_mish: block %u too short for valid mish block\n", *mishblocknum); |
501 | 5 | free(decoded); |
502 | 5 | return CL_EFORMAT; |
503 | 5 | } |
504 | | /* mish check: magic is mish, have to check after conversion from base64 |
505 | | * mish base64 is bWlzaA [but last character can change last two bytes] |
506 | | * won't see that in practice much (affects value of version field) */ |
507 | 72 | if (memcmp(decoded, mish_magic, 4)) { |
508 | 5 | cli_dbgmsg("dmg_decode_mish: block %u does not have mish magic\n", *mishblocknum); |
509 | 5 | free(decoded); |
510 | 5 | return CL_EFORMAT; |
511 | 5 | } |
512 | | |
513 | 67 | mish_set->mish = (struct dmg_mish_block *)decoded; |
514 | 67 | mish_set->mish->startSector = be64_to_host(mish_set->mish->startSector); |
515 | 67 | mish_set->mish->sectorCount = be64_to_host(mish_set->mish->sectorCount); |
516 | 67 | mish_set->mish->dataOffset = be64_to_host(mish_set->mish->dataOffset); |
517 | | // mish_set->mish->bufferCount = be32_to_host(mish_set->mish->bufferCount); |
518 | 67 | mish_set->mish->blockDataCount = be32_to_host(mish_set->mish->blockDataCount); |
519 | | |
520 | 67 | cli_dbgmsg("dmg_decode_mish: startSector = " STDu64 " sectorCount = " STDu64 |
521 | 67 | " dataOffset = " STDu64 " stripeCount = " STDu32 "\n", |
522 | 67 | mish_set->mish->startSector, mish_set->mish->sectorCount, |
523 | 67 | mish_set->mish->dataOffset, mish_set->mish->blockDataCount); |
524 | | |
525 | | /* decoded length should be mish block + blockDataCount * 40 */ |
526 | 67 | if (decoded_len < (sizeof(struct dmg_mish_block) + mish_set->mish->blockDataCount * sizeof(struct dmg_block_data))) { |
527 | 17 | cli_dbgmsg("dmg_decode_mish: mish block %u too small\n", *mishblocknum); |
528 | 17 | free(decoded); |
529 | 17 | mish_set->mish = NULL; |
530 | 17 | return CL_EFORMAT; |
531 | 50 | } else if (decoded_len > (sizeof(struct dmg_mish_block) + mish_set->mish->blockDataCount * sizeof(struct dmg_block_data))) { |
532 | 13 | cli_dbgmsg("dmg_decode_mish: mish block %u bigger than needed, continuing\n", *mishblocknum); |
533 | 13 | } |
534 | | |
535 | 50 | mish_set->stripes = (struct dmg_block_data *)(decoded + sizeof(struct dmg_mish_block)); |
536 | 50 | return CL_CLEAN; |
537 | 67 | } |
538 | | #endif |
539 | | |
540 | | /* Comparator for stripe sorting */ |
541 | | static int cmp_mish_stripes(const void *stripe_a, const void *stripe_b) |
542 | 2 | { |
543 | 2 | const struct dmg_block_data *a = stripe_a, *b = stripe_b; |
544 | 2 | return a->startSector - b->startSector; |
545 | 2 | } |
546 | | |
547 | | /* Safely track sector sizes for output estimate */ |
548 | | static int dmg_track_sectors(uint64_t *total, uint8_t *data_to_write, |
549 | | uint32_t stripeNum, uint32_t stripeType, uint64_t stripeCount) |
550 | 47 | { |
551 | 47 | int ret = CL_CLEAN, usable = 0; |
552 | | |
553 | 47 | switch (stripeType) { |
554 | 0 | case DMG_STRIPE_STORED: |
555 | 0 | *data_to_write = 1; |
556 | 0 | usable = 1; |
557 | 0 | break; |
558 | 2 | case DMG_STRIPE_ADC: |
559 | 2 | *data_to_write = 1; |
560 | 2 | usable = 1; |
561 | 2 | break; |
562 | 14 | case DMG_STRIPE_DEFLATE: |
563 | 14 | #if HAVE_LIBZ |
564 | 14 | *data_to_write = 1; |
565 | 14 | usable = 1; |
566 | | #else |
567 | | cli_warnmsg("dmg_track_sectors: Need zlib decompression to properly scan this file.\n"); |
568 | | return CL_EFORMAT; |
569 | | #endif |
570 | 14 | break; |
571 | 0 | case DMG_STRIPE_BZ: |
572 | 0 | #if HAVE_BZLIB_H |
573 | 0 | *data_to_write = 1; |
574 | 0 | usable = 1; |
575 | | #else |
576 | | cli_warnmsg("dmg_track_sectors: Need bzip2 decompression to properly scan this file.\n"); |
577 | | return CL_EFORMAT; |
578 | | #endif |
579 | 0 | break; |
580 | 0 | case DMG_STRIPE_EMPTY: |
581 | 5 | case DMG_STRIPE_ZEROES: |
582 | | /* Usable, but only zeroes is not worth scanning on its own */ |
583 | 5 | usable = 1; |
584 | 5 | break; |
585 | 0 | case DMG_STRIPE_SKIP: |
586 | 23 | case DMG_STRIPE_END: |
587 | | /* These should be sectorCount 0 */ |
588 | 23 | break; |
589 | 3 | default: |
590 | 3 | if (stripeCount) { |
591 | | /* Continue for now */ |
592 | 3 | cli_dbgmsg("dmg_track_sectors: unknown type on stripe " STDu32 ", will skip\n", stripeNum); |
593 | 3 | } else { |
594 | | /* Continue, no sectors missed */ |
595 | 0 | cli_dbgmsg("dmg_track_sectors: unknown type on empty stripe " STDu32 "\n", stripeNum); |
596 | 0 | } |
597 | 3 | break; |
598 | 47 | } |
599 | | |
600 | 47 | if (usable) { |
601 | | /* Check for wrap */ |
602 | 21 | if (*total < (*total + stripeCount)) { |
603 | 21 | *total = *total + stripeCount; |
604 | 21 | } else if (stripeCount) { |
605 | 0 | cli_dbgmsg("dmg_track_sectors: *total would wrap uint64, suspicious\n"); |
606 | 0 | ret = CL_EFORMAT; |
607 | 0 | } else { |
608 | | /* Can continue */ |
609 | 0 | cli_dbgmsg("dmg_track_sectors: unexpected zero sectorCount on stripe " STDu32 "\n", stripeNum); |
610 | 0 | } |
611 | 21 | } |
612 | | |
613 | 47 | return ret; |
614 | 47 | } |
615 | | |
616 | | /* Stripe handling: zero block (type 0x0 or 0x2) */ |
617 | | static int dmg_stripe_zeroes(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set) |
618 | 0 | { |
619 | 0 | int ret = CL_CLEAN; |
620 | 0 | size_t len = mish_set->stripes[index].sectorCount * DMG_SECTOR_SIZE; |
621 | 0 | size_t written; |
622 | 0 | uint8_t obuf[BUFSIZ]; |
623 | |
|
624 | 0 | UNUSEDPARAM(ctx); |
625 | |
|
626 | 0 | cli_dbgmsg("dmg_stripe_zeroes: stripe " STDu32 "\n", index); |
627 | 0 | if (len == 0) |
628 | 0 | return CL_CLEAN; |
629 | | |
630 | 0 | memset(obuf, 0, sizeof(obuf)); |
631 | 0 | while (len > sizeof(obuf)) { |
632 | 0 | written = cli_writen(fd, obuf, sizeof(obuf)); |
633 | 0 | if (written != sizeof(obuf)) { |
634 | 0 | ret = CL_EWRITE; |
635 | 0 | break; |
636 | 0 | } |
637 | 0 | len -= sizeof(obuf); |
638 | 0 | } |
639 | |
|
640 | 0 | if ((ret == CL_CLEAN) && (len > 0)) { |
641 | 0 | written = cli_writen(fd, obuf, len); |
642 | 0 | if (written != len) { |
643 | 0 | ret = CL_EWRITE; |
644 | 0 | } |
645 | 0 | } |
646 | |
|
647 | 0 | if (ret != CL_CLEAN) { |
648 | 0 | cli_errmsg("dmg_stripe_zeroes: error writing bytes to file (out of disk space?)\n"); |
649 | 0 | return CL_EWRITE; |
650 | 0 | } |
651 | 0 | return CL_CLEAN; |
652 | 0 | } |
653 | | |
654 | | /* Stripe handling: stored block (type 0x1) */ |
655 | | static int dmg_stripe_store(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set) |
656 | 0 | { |
657 | 0 | const void *obuf; |
658 | 0 | size_t off = mish_set->stripes[index].dataOffset; |
659 | 0 | size_t len = mish_set->stripes[index].dataLength; |
660 | 0 | size_t written; |
661 | |
|
662 | 0 | cli_dbgmsg("dmg_stripe_store: stripe " STDu32 "\n", index); |
663 | 0 | if (len == 0) |
664 | 0 | return CL_CLEAN; |
665 | | |
666 | 0 | obuf = (void *)fmap_need_off_once(ctx->fmap, off, len); |
667 | 0 | if (!obuf) { |
668 | 0 | cli_warnmsg("dmg_stripe_store: fmap need failed on stripe " STDu32 "\n", index); |
669 | 0 | return CL_EMAP; |
670 | 0 | } |
671 | 0 | written = cli_writen(fd, obuf, len); |
672 | 0 | if (written == (size_t)-1) { |
673 | 0 | cli_errmsg("dmg_stripe_store: error writing bytes to file (out of disk space?)\n"); |
674 | 0 | return CL_EWRITE; |
675 | 0 | } else if (written != len) { |
676 | 0 | cli_errmsg("dmg_stripe_store: error writing bytes to file (out of disk space?)\n"); |
677 | 0 | return CL_EWRITE; |
678 | 0 | } |
679 | 0 | return CL_CLEAN; |
680 | 0 | } |
681 | | |
682 | | /* Stripe handling: ADC block (type 0x80000004) */ |
683 | | static int dmg_stripe_adc(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set) |
684 | 1 | { |
685 | 1 | int adcret; |
686 | 1 | adc_stream strm; |
687 | 1 | size_t off = mish_set->stripes[index].dataOffset; |
688 | 1 | size_t len = mish_set->stripes[index].dataLength; |
689 | 1 | uint64_t size_so_far = 0; |
690 | 1 | uint64_t expected_len = mish_set->stripes[index].sectorCount * DMG_SECTOR_SIZE; |
691 | 1 | uint8_t obuf[BUFSIZ]; |
692 | | |
693 | 1 | cli_dbgmsg("dmg_stripe_adc: stripe " STDu32 " initial len " STDu64 " expected len " STDu64 "\n", |
694 | 1 | index, (uint64_t)len, (uint64_t)expected_len); |
695 | 1 | if (len == 0) |
696 | 0 | return CL_CLEAN; |
697 | | |
698 | 1 | memset(&strm, 0, sizeof(strm)); |
699 | 1 | strm.next_in = (uint8_t *)fmap_need_off_once(ctx->fmap, off, len); |
700 | 1 | if (!strm.next_in) { |
701 | 0 | cli_warnmsg("dmg_stripe_adc: fmap need failed on stripe " STDu32 "\n", index); |
702 | 0 | return CL_EMAP; |
703 | 0 | } |
704 | 1 | strm.avail_in = len; |
705 | 1 | strm.next_out = obuf; |
706 | 1 | strm.avail_out = sizeof(obuf); |
707 | | |
708 | 1 | adcret = adc_decompressInit(&strm); |
709 | 1 | if (adcret != ADC_OK) { |
710 | 0 | cli_warnmsg("dmg_stripe_adc: adc_decompressInit failed\n"); |
711 | 0 | return CL_EMEM; |
712 | 0 | } |
713 | | |
714 | 1 | while (adcret == ADC_OK) { |
715 | 1 | size_t written; |
716 | 1 | if (size_so_far > expected_len) { |
717 | 0 | cli_warnmsg("dmg_stripe_adc: expected size exceeded!\n"); |
718 | 0 | adc_decompressEnd(&strm); |
719 | 0 | return CL_EFORMAT; |
720 | 0 | } |
721 | 1 | adcret = adc_decompress(&strm); |
722 | 1 | switch (adcret) { |
723 | 0 | case ADC_OK: |
724 | 0 | if (strm.avail_out == 0) { |
725 | 0 | if ((written = cli_writen(fd, obuf, sizeof(obuf))) != sizeof(obuf)) { |
726 | 0 | cli_errmsg("dmg_stripe_adc: failed write to output file\n"); |
727 | 0 | adc_decompressEnd(&strm); |
728 | 0 | return CL_EWRITE; |
729 | 0 | } |
730 | 0 | size_so_far += written; |
731 | 0 | strm.next_out = obuf; |
732 | 0 | strm.avail_out = sizeof(obuf); |
733 | 0 | } |
734 | 0 | continue; |
735 | 0 | case ADC_STREAM_END: |
736 | 1 | default: |
737 | 1 | written = sizeof(obuf) - strm.avail_out; |
738 | 1 | if (written) { |
739 | 1 | if ((cli_writen(fd, obuf, written)) != written) { |
740 | 0 | cli_errmsg("dmg_stripe_adc: failed write to output file\n"); |
741 | 0 | adc_decompressEnd(&strm); |
742 | 0 | return CL_EWRITE; |
743 | 0 | } |
744 | 1 | size_so_far += written; |
745 | 1 | strm.next_out = obuf; |
746 | 1 | strm.avail_out = sizeof(obuf); |
747 | 1 | } |
748 | 1 | if (adcret == ADC_STREAM_END) |
749 | 0 | break; |
750 | 1 | cli_dbgmsg("dmg_stripe_adc: after writing " STDu64 " bytes, " |
751 | 1 | "got error %d decompressing stripe " STDu32 "\n", |
752 | 1 | size_so_far, adcret, index); |
753 | 1 | adc_decompressEnd(&strm); |
754 | 1 | return CL_EFORMAT; |
755 | 1 | } |
756 | 0 | break; |
757 | 1 | } |
758 | | |
759 | 0 | adc_decompressEnd(&strm); |
760 | 0 | cli_dbgmsg("dmg_stripe_adc: stripe " STDu32 " actual len " STDu64 " expected len " STDu64 "\n", |
761 | 0 | index, size_so_far, expected_len); |
762 | 0 | return CL_CLEAN; |
763 | 1 | } |
764 | | |
765 | | /* Stripe handling: deflate block (type 0x80000005) */ |
766 | | static int dmg_stripe_inflate(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set) |
767 | 13 | { |
768 | 13 | int zstat; |
769 | 13 | z_stream strm; |
770 | 13 | size_t off = mish_set->stripes[index].dataOffset; |
771 | 13 | size_t len = mish_set->stripes[index].dataLength; |
772 | 13 | uint64_t size_so_far = 0; |
773 | 13 | uint64_t expected_len = mish_set->stripes[index].sectorCount * DMG_SECTOR_SIZE; |
774 | 13 | uint8_t obuf[BUFSIZ]; |
775 | | |
776 | 13 | cli_dbgmsg("dmg_stripe_inflate: stripe " STDu32 "\n", index); |
777 | 13 | if (len == 0) |
778 | 0 | return CL_CLEAN; |
779 | | |
780 | 13 | memset(&strm, 0, sizeof(strm)); |
781 | 13 | strm.next_in = (void *)fmap_need_off_once(ctx->fmap, off, len); |
782 | 13 | if (!strm.next_in) { |
783 | 0 | cli_warnmsg("dmg_stripe_inflate: fmap need failed on stripe " STDu32 "\n", index); |
784 | 0 | return CL_EMAP; |
785 | 0 | } |
786 | 13 | strm.avail_in = len; |
787 | 13 | strm.next_out = obuf; |
788 | 13 | strm.avail_out = sizeof(obuf); |
789 | | |
790 | 13 | zstat = inflateInit(&strm); |
791 | 13 | if (zstat != Z_OK) { |
792 | 0 | cli_warnmsg("dmg_stripe_inflate: inflateInit failed\n"); |
793 | 0 | return CL_EMEM; |
794 | 0 | } |
795 | | |
796 | 26 | while (strm.avail_in) { |
797 | 24 | size_t written; |
798 | 24 | if (size_so_far > expected_len) { |
799 | 0 | cli_warnmsg("dmg_stripe_inflate: expected size exceeded!\n"); |
800 | 0 | inflateEnd(&strm); |
801 | 0 | return CL_EFORMAT; |
802 | 0 | } |
803 | 24 | zstat = inflate(&strm, Z_NO_FLUSH); /* zlib */ |
804 | 24 | switch (zstat) { |
805 | 13 | case Z_OK: |
806 | 13 | if (strm.avail_out == 0) { |
807 | 11 | if ((written = cli_writen(fd, obuf, sizeof(obuf))) != sizeof(obuf)) { |
808 | 0 | cli_errmsg("dmg_stripe_inflate: failed write to output file\n"); |
809 | 0 | inflateEnd(&strm); |
810 | 0 | return CL_EWRITE; |
811 | 0 | } |
812 | 11 | size_so_far += written; |
813 | 11 | strm.next_out = (Bytef *)obuf; |
814 | 11 | strm.avail_out = sizeof(obuf); |
815 | 11 | } |
816 | 13 | continue; |
817 | 13 | case Z_STREAM_END: |
818 | 11 | default: |
819 | 11 | written = sizeof(obuf) - strm.avail_out; |
820 | 11 | if (written) { |
821 | 9 | if ((cli_writen(fd, obuf, written)) != written) { |
822 | 0 | cli_errmsg("dmg_stripe_inflate: failed write to output file\n"); |
823 | 0 | inflateEnd(&strm); |
824 | 0 | return CL_EWRITE; |
825 | 0 | } |
826 | 9 | size_so_far += written; |
827 | 9 | strm.next_out = (Bytef *)obuf; |
828 | 9 | strm.avail_out = sizeof(obuf); |
829 | 9 | if (zstat == Z_STREAM_END) |
830 | 7 | break; |
831 | 9 | } |
832 | 4 | if (strm.msg) |
833 | 4 | cli_dbgmsg("dmg_stripe_inflate: after writing " STDu64 " bytes, " |
834 | 4 | "got error \"%s\" inflating stripe " STDu32 "\n", |
835 | 4 | size_so_far, strm.msg, index); |
836 | 0 | else |
837 | 0 | cli_dbgmsg("dmg_stripe_inflate: after writing " STDu64 " bytes, " |
838 | 0 | "got error %d inflating stripe " STDu32 "\n", |
839 | 0 | size_so_far, zstat, index); |
840 | 4 | inflateEnd(&strm); |
841 | 4 | return CL_EFORMAT; |
842 | 24 | } |
843 | 7 | break; |
844 | 24 | } |
845 | | |
846 | 9 | if (strm.avail_out != sizeof(obuf)) { |
847 | 2 | if (cli_writen(fd, obuf, sizeof(obuf) - strm.avail_out) == (size_t)-1) { |
848 | 0 | cli_errmsg("dmg_stripe_inflate: failed write to output file\n"); |
849 | 0 | inflateEnd(&strm); |
850 | 0 | return CL_EWRITE; |
851 | 0 | } |
852 | 2 | } |
853 | | |
854 | 9 | inflateEnd(&strm); |
855 | 9 | return CL_CLEAN; |
856 | 9 | } |
857 | | |
858 | | /* Stripe handling: bzip block (type 0x80000006) */ |
859 | | static int dmg_stripe_bzip(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set) |
860 | 0 | { |
861 | 0 | int ret = CL_CLEAN; |
862 | 0 | size_t off = mish_set->stripes[index].dataOffset; |
863 | 0 | size_t len = mish_set->stripes[index].dataLength; |
864 | 0 | uint64_t size_so_far = 0; |
865 | 0 | uint64_t expected_len = mish_set->stripes[index].sectorCount * DMG_SECTOR_SIZE; |
866 | 0 | #if HAVE_BZLIB_H |
867 | 0 | int rc; |
868 | 0 | bz_stream strm; |
869 | 0 | uint8_t obuf[BUFSIZ]; |
870 | 0 | #endif |
871 | |
|
872 | 0 | cli_dbgmsg("dmg_stripe_bzip: stripe " STDu32 " initial len " STDu64 " expected len " STDu64 "\n", |
873 | 0 | index, (uint64_t)len, (uint64_t)expected_len); |
874 | |
|
875 | 0 | #if HAVE_BZLIB_H |
876 | 0 | memset(&strm, 0, sizeof(strm)); |
877 | 0 | strm.next_out = (char *)obuf; |
878 | 0 | strm.avail_out = sizeof(obuf); |
879 | 0 | if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) { |
880 | 0 | cli_dbgmsg("dmg_stripe_bzip: bzDecompressInit failed\n"); |
881 | 0 | return CL_EOPEN; |
882 | 0 | } |
883 | | |
884 | 0 | do { |
885 | 0 | if (size_so_far > expected_len) { |
886 | 0 | cli_warnmsg("dmg_stripe_bzip: expected size exceeded!\n"); |
887 | 0 | ret = CL_EFORMAT; |
888 | 0 | break; |
889 | 0 | } |
890 | 0 | if (strm.avail_in == 0) { |
891 | 0 | size_t next_len = (len > sizeof(obuf)) ? sizeof(obuf) : len; |
892 | 0 | dmg_bzipmsg("dmg_stripe_bzip: off %lu len %lu next_len %lu\n", off, len, next_len); |
893 | 0 | strm.next_in = (void *)fmap_need_off_once(ctx->fmap, off, next_len); |
894 | 0 | if (strm.next_in == NULL) { |
895 | 0 | cli_dbgmsg("dmg_stripe_bzip: expected more stream\n"); |
896 | 0 | ret = CL_EMAP; |
897 | 0 | break; |
898 | 0 | } |
899 | 0 | strm.avail_in = next_len; |
900 | 0 | len -= next_len; |
901 | 0 | off += next_len; |
902 | 0 | } |
903 | | |
904 | 0 | dmg_bzipmsg("dmg_stripe_bzip: before = strm.avail_in %lu strm.avail_out: %lu\n", strm.avail_in, strm.avail_out); |
905 | 0 | rc = BZ2_bzDecompress(&strm); |
906 | 0 | if ((rc != BZ_OK) && (rc != BZ_STREAM_END)) { |
907 | 0 | cli_dbgmsg("dmg_stripe_bzip: decompress error: %d\n", rc); |
908 | 0 | ret = CL_EFORMAT; |
909 | 0 | break; |
910 | 0 | } |
911 | | |
912 | 0 | dmg_bzipmsg("dmg_stripe_bzip: after = strm.avail_in %lu strm.avail_out: %lu rc: %d %d\n", |
913 | 0 | strm.avail_in, strm.avail_out, rc, BZ_STREAM_END); |
914 | | /* Drain output buffer */ |
915 | 0 | if (!strm.avail_out) { |
916 | 0 | size_t next_write = sizeof(obuf); |
917 | 0 | do { |
918 | 0 | size_so_far += next_write; |
919 | 0 | dmg_bzipmsg("dmg_stripe_bzip: size_so_far: " STDu64 " next_write: %zu\n", size_so_far, next_write); |
920 | 0 | if (size_so_far > expected_len) { |
921 | 0 | cli_warnmsg("dmg_stripe_bzip: expected size exceeded!\n"); |
922 | 0 | ret = CL_EFORMAT; |
923 | 0 | rc = BZ_DATA_ERROR; /* prevent stream end block */ |
924 | 0 | break; |
925 | 0 | } |
926 | | |
927 | 0 | ret = cli_checklimits("dmg_stripe_bzip", ctx, (unsigned long)(size_so_far + sizeof(obuf)), 0, 0); |
928 | 0 | if (ret != CL_CLEAN) { |
929 | 0 | break; |
930 | 0 | } |
931 | | |
932 | 0 | if (cli_writen(fd, obuf, next_write) != next_write) { |
933 | 0 | cli_dbgmsg("dmg_stripe_bzip: error writing to tmpfile\n"); |
934 | 0 | ret = CL_EWRITE; |
935 | 0 | break; |
936 | 0 | } |
937 | | |
938 | 0 | strm.next_out = (char *)obuf; |
939 | 0 | strm.avail_out = sizeof(obuf); |
940 | |
|
941 | 0 | if (rc == BZ_OK) |
942 | 0 | rc = BZ2_bzDecompress(&strm); |
943 | 0 | if ((rc != BZ_OK) && (rc != BZ_STREAM_END)) { |
944 | 0 | cli_dbgmsg("dmg_stripe_bzip: decompress error: %d\n", rc); |
945 | 0 | ret = CL_EFORMAT; |
946 | 0 | break; |
947 | 0 | } |
948 | 0 | } while (!strm.avail_out); |
949 | 0 | } |
950 | | /* Stream end, so write data if any remains in buffer */ |
951 | 0 | if (rc == BZ_STREAM_END) { |
952 | 0 | size_t next_write = sizeof(obuf) - strm.avail_out; |
953 | 0 | size_so_far += next_write; |
954 | 0 | dmg_bzipmsg("dmg_stripe_bzip: size_so_far: " STDu64 " next_write: %zu\n", size_so_far, next_write); |
955 | |
|
956 | 0 | ret = cli_checklimits("dmg_stripe_bzip", ctx, (unsigned long)(size_so_far + sizeof(obuf)), 0, 0); |
957 | 0 | if (ret != CL_CLEAN) { |
958 | 0 | break; |
959 | 0 | } |
960 | | |
961 | 0 | if (cli_writen(fd, obuf, next_write) != next_write) { |
962 | 0 | cli_dbgmsg("dmg_stripe_bzip: error writing to tmpfile\n"); |
963 | 0 | ret = CL_EWRITE; |
964 | 0 | break; |
965 | 0 | } |
966 | | |
967 | 0 | strm.next_out = (char *)obuf; |
968 | 0 | strm.avail_out = sizeof(obuf); |
969 | 0 | } |
970 | 0 | } while ((rc == BZ_OK) && (len > 0)); |
971 | | |
972 | 0 | BZ2_bzDecompressEnd(&strm); |
973 | 0 | #endif |
974 | |
|
975 | 0 | if (ret == CL_CLEAN) { |
976 | 0 | if (size_so_far != expected_len) { |
977 | 0 | cli_dbgmsg("dmg_stripe_bzip: output does not match expected size!\n"); |
978 | 0 | } |
979 | 0 | } |
980 | 0 | return ret; |
981 | 0 | } |
982 | | |
983 | | /* Given mish data, reconstruct the partition details */ |
984 | | static int dmg_handle_mish(cli_ctx *ctx, unsigned int mishblocknum, char *dir, |
985 | | uint64_t xmlOffset, struct dmg_mish_with_stripes *mish_set) |
986 | 36 | { |
987 | 36 | struct dmg_block_data *blocklist = mish_set->stripes; |
988 | 36 | uint64_t totalSectors = 0; |
989 | 36 | uint32_t i; |
990 | 36 | unsigned long projected_size; |
991 | 36 | int ret = CL_CLEAN, ofd; |
992 | 36 | uint8_t sorted = 1, writeable_data = 0; |
993 | 36 | char outfile[PATH_MAX + 1]; |
994 | | |
995 | | /* First loop, fix endian-ness and check if already sorted */ |
996 | 83 | for (i = 0; i < mish_set->mish->blockDataCount; i++) { |
997 | 51 | blocklist[i].type = be32_to_host(blocklist[i].type); |
998 | | // blocklist[i].reserved = be32_to_host(blocklist[i].reserved); |
999 | 51 | blocklist[i].startSector = be64_to_host(blocklist[i].startSector); |
1000 | 51 | blocklist[i].sectorCount = be64_to_host(blocklist[i].sectorCount); |
1001 | 51 | blocklist[i].dataOffset = be64_to_host(blocklist[i].dataOffset); |
1002 | 51 | blocklist[i].dataLength = be64_to_host(blocklist[i].dataLength); |
1003 | 51 | cli_dbgmsg("mish %u stripe " STDu32 " type " STDx32 " start " STDu64 |
1004 | 51 | " count " STDu64 " source " STDu64 " length " STDu64 "\n", |
1005 | 51 | mishblocknum, i, blocklist[i].type, blocklist[i].startSector, blocklist[i].sectorCount, |
1006 | 51 | blocklist[i].dataOffset, blocklist[i].dataLength); |
1007 | 51 | if ((blocklist[i].dataOffset > xmlOffset) || |
1008 | 51 | (blocklist[i].dataOffset + blocklist[i].dataLength > xmlOffset)) { |
1009 | 4 | cli_dbgmsg("dmg_handle_mish: invalid stripe offset and/or length\n"); |
1010 | 4 | return CL_EFORMAT; |
1011 | 4 | } |
1012 | 47 | if ((i > 0) && sorted && (blocklist[i].startSector < blocklist[i - 1].startSector)) { |
1013 | 2 | cli_dbgmsg("dmg_handle_mish: stripes not in order, will have to sort\n"); |
1014 | 2 | sorted = 0; |
1015 | 2 | } |
1016 | 47 | if (dmg_track_sectors(&totalSectors, &writeable_data, i, blocklist[i].type, blocklist[i].sectorCount)) { |
1017 | | /* reason was logged from dmg_track_sector_count */ |
1018 | 0 | return CL_EFORMAT; |
1019 | 0 | } |
1020 | 47 | } |
1021 | | |
1022 | 32 | if (!sorted) { |
1023 | 2 | cli_qsort(blocklist, mish_set->mish->blockDataCount, sizeof(struct dmg_block_data), cmp_mish_stripes); |
1024 | 2 | } |
1025 | 32 | cli_dbgmsg("dmg_handle_mish: stripes in order!\n"); |
1026 | | |
1027 | | /* Size checks */ |
1028 | 32 | if ((writeable_data == 0) || (totalSectors == 0)) { |
1029 | 17 | cli_dbgmsg("dmg_handle_mish: no data to output\n"); |
1030 | 17 | return CL_CLEAN; |
1031 | 17 | } else if (totalSectors > (ULONG_MAX / DMG_SECTOR_SIZE)) { |
1032 | | /* cli_checklimits only takes unsigned long for now */ |
1033 | 0 | cli_warnmsg("dmg_handle_mish: mish block %u too big to handle (for now)", mishblocknum); |
1034 | 0 | return CL_CLEAN; |
1035 | 0 | } |
1036 | 15 | projected_size = (unsigned long)(totalSectors * DMG_SECTOR_SIZE); |
1037 | 15 | ret = cli_checklimits("cli_scandmg", ctx, projected_size, 0, 0); |
1038 | 15 | if (ret != CL_CLEAN) { |
1039 | | /* limits exceeded */ |
1040 | 1 | cli_dbgmsg("dmg_handle_mish: skipping block %u, limits exceeded\n", mishblocknum); |
1041 | 1 | return ret; |
1042 | 1 | } |
1043 | | |
1044 | | /* Prepare for file */ |
1045 | 14 | snprintf(outfile, sizeof(outfile) - 1, "%s" PATHSEP "dmg%02u", dir, mishblocknum); |
1046 | 14 | outfile[sizeof(outfile) - 1] = '\0'; |
1047 | 14 | ofd = open(outfile, O_RDWR | O_CREAT | O_EXCL | O_TRUNC | O_BINARY, 0600); |
1048 | 14 | if (ofd < 0) { |
1049 | 0 | char err[128]; |
1050 | 0 | cli_errmsg("cli_scandmg: Can't create temporary file %s: %s\n", |
1051 | 0 | outfile, cli_strerror(errno, err, sizeof(err))); |
1052 | 0 | return CL_ETMPFILE; |
1053 | 0 | } |
1054 | 14 | cli_dbgmsg("dmg_handle_mish: extracting block %u to %s\n", mishblocknum, outfile); |
1055 | | |
1056 | | /* Push data, stripe by stripe */ |
1057 | 37 | for (i = 0; i < mish_set->mish->blockDataCount && ret == CL_CLEAN; i++) { |
1058 | 23 | switch (blocklist[i].type) { |
1059 | 0 | case DMG_STRIPE_EMPTY: |
1060 | 0 | case DMG_STRIPE_ZEROES: |
1061 | 0 | ret = dmg_stripe_zeroes(ctx, ofd, i, mish_set); |
1062 | 0 | break; |
1063 | 0 | case DMG_STRIPE_STORED: |
1064 | 0 | ret = dmg_stripe_store(ctx, ofd, i, mish_set); |
1065 | 0 | break; |
1066 | 1 | case DMG_STRIPE_ADC: |
1067 | 1 | ret = dmg_stripe_adc(ctx, ofd, i, mish_set); |
1068 | 1 | break; |
1069 | 13 | case DMG_STRIPE_DEFLATE: |
1070 | 13 | ret = dmg_stripe_inflate(ctx, ofd, i, mish_set); |
1071 | 13 | break; |
1072 | 0 | case DMG_STRIPE_BZ: |
1073 | 0 | ret = dmg_stripe_bzip(ctx, ofd, i, mish_set); |
1074 | 0 | break; |
1075 | 0 | case DMG_STRIPE_SKIP: |
1076 | 9 | case DMG_STRIPE_END: |
1077 | 9 | default: |
1078 | 9 | cli_dbgmsg("dmg_handle_mish: stripe " STDu32 ", skipped\n", i); |
1079 | 9 | break; |
1080 | 23 | } |
1081 | 23 | } |
1082 | | |
1083 | | /* If okay so far, scan rebuilt partition */ |
1084 | 14 | if (ret == CL_CLEAN) { |
1085 | | /* Have to keep partition typing separate */ |
1086 | 9 | ret = cli_magic_scan_desc_type(ofd, outfile, ctx, CL_TYPE_PART_ANY, NULL, LAYER_ATTRIBUTES_NONE); |
1087 | 9 | } |
1088 | | |
1089 | 14 | close(ofd); |
1090 | 14 | if (!ctx->engine->keeptmp) |
1091 | 14 | if (cli_unlink(outfile)) return CL_EUNLINK; |
1092 | | |
1093 | 14 | return ret; |
1094 | 14 | } |
1095 | | |
1096 | | static int dmg_extract_xml(cli_ctx *ctx, char *dir, struct dmg_koly_block *hdr) |
1097 | 0 | { |
1098 | 0 | char *xmlfile; |
1099 | 0 | const char *outdata; |
1100 | 0 | size_t namelen, nread; |
1101 | 0 | int ofd; |
1102 | | |
1103 | | /* Prep TOC XML for output */ |
1104 | 0 | outdata = fmap_need_off_once_len(ctx->fmap, hdr->xmlOffset, hdr->xmlLength, &nread); |
1105 | 0 | if (!outdata || (nread != hdr->xmlLength)) { |
1106 | 0 | cli_errmsg("cli_scandmg: Failed getting XML from map, len " STDu64 "\n", hdr->xmlLength); |
1107 | 0 | return CL_EMAP; |
1108 | 0 | } |
1109 | | |
1110 | 0 | namelen = strlen(dir) + 1 + 7 + 1; |
1111 | 0 | if (!(xmlfile = cli_malloc(namelen))) { |
1112 | 0 | return CL_EMEM; |
1113 | 0 | } |
1114 | 0 | snprintf(xmlfile, namelen, "%s" PATHSEP "toc.xml", dir); |
1115 | 0 | cli_dbgmsg("cli_scandmg: Extracting XML as %s\n", xmlfile); |
1116 | | |
1117 | | /* Write out TOC XML */ |
1118 | 0 | if ((ofd = open(xmlfile, O_CREAT | O_RDWR | O_EXCL | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) { |
1119 | 0 | char err[128]; |
1120 | 0 | cli_errmsg("cli_scandmg: Can't create temporary file %s: %s\n", |
1121 | 0 | xmlfile, cli_strerror(errno, err, sizeof(err))); |
1122 | 0 | free(xmlfile); |
1123 | 0 | return CL_ETMPFILE; |
1124 | 0 | } |
1125 | | |
1126 | 0 | if ((uint64_t)cli_writen(ofd, outdata, (size_t)hdr->xmlLength) != hdr->xmlLength) { |
1127 | 0 | cli_errmsg("cli_scandmg: Not all bytes written!\n"); |
1128 | 0 | close(ofd); |
1129 | 0 | free(xmlfile); |
1130 | 0 | return CL_EWRITE; |
1131 | 0 | } |
1132 | | |
1133 | 0 | close(ofd); |
1134 | 0 | free(xmlfile); |
1135 | 0 | return CL_SUCCESS; |
1136 | 0 | } |