VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/checksum/manifest.cpp@ 105589

Last change on this file since 105589 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.5 KB
Line 
1/* $Id: manifest.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * IPRT - Manifest file handling, old style - deprecated.
4 */
5
6/*
7 * Copyright (C) 2009-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include "internal/iprt.h"
42#include <iprt/manifest.h>
43
44#include <iprt/err.h>
45#include <iprt/file.h>
46#include <iprt/mem.h>
47#include <iprt/path.h>
48#include <iprt/sha.h>
49#include <iprt/stream.h>
50#include <iprt/string.h>
51
52
53/*********************************************************************************************************************************
54* Structures and Typedefs *
55*********************************************************************************************************************************/
56/**
57 * Internal per file structure used by RTManifestVerify
58 */
59typedef struct RTMANIFESTFILEENTRY
60{
61 char *pszManifestFile;
62 char *pszManifestDigest;
63 PRTMANIFESTTEST pTestPattern;
64} RTMANIFESTFILEENTRY;
65typedef RTMANIFESTFILEENTRY* PRTMANIFESTFILEENTRY;
66
67/**
68 * Internal structure used for the progress callback
69 */
70typedef struct RTMANIFESTCALLBACKDATA
71{
72 PFNRTPROGRESS pfnProgressCallback;
73 void *pvUser;
74 size_t cMaxFiles;
75 size_t cCurrentFile;
76} RTMANIFESTCALLBACKDATA;
77typedef RTMANIFESTCALLBACKDATA* PRTMANIFESTCALLBACKDATA;
78
79
80/*******************************************************************************
81* Private functions
82*******************************************************************************/
83
84DECLINLINE(char *) rtManifestPosOfCharInBuf(char const *pv, size_t cb, char c)
85{
86 char *pb = (char *)pv;
87 for (; cb; --cb, ++pb)
88 if (RT_UNLIKELY(*pb == c))
89 return pb;
90 return NULL;
91}
92
93DECLINLINE(size_t) rtManifestIndexOfCharInBuf(char const *pv, size_t cb, char c)
94{
95 char const *pb = (char const *)pv;
96 for (size_t i=0; i < cb; ++i, ++pb)
97 if (RT_UNLIKELY(*pb == c))
98 return i;
99 return cb;
100}
101
102static DECLCALLBACK(int) rtSHAProgressCallback(unsigned uPercent, void *pvUser)
103{
104 PRTMANIFESTCALLBACKDATA pData = (PRTMANIFESTCALLBACKDATA)pvUser;
105 return pData->pfnProgressCallback((unsigned)( (uPercent + (float)pData->cCurrentFile * 100.0)
106 / (float)pData->cMaxFiles),
107 pData->pvUser);
108}
109
110
111/*******************************************************************************
112* Public functions
113*******************************************************************************/
114
115RTR3DECL(int) RTManifestVerify(const char *pszManifestFile, PRTMANIFESTTEST paTests, size_t cTests, size_t *piFailed)
116{
117 /* Validate input */
118 AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER);
119
120 /* Open the manifest file */
121 RTFILE file;
122 int rc = RTFileOpen(&file, pszManifestFile, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE);
123 if (RT_FAILURE(rc))
124 return rc;
125
126 void *pvBuf = 0;
127 do
128 {
129 uint64_t cbSize;
130 rc = RTFileQuerySize(file, &cbSize);
131 if (RT_FAILURE(rc))
132 break;
133
134 /* Cast down for the case size_t < uint64_t. This isn't really correct,
135 but we consider manifest files bigger than size_t as not supported
136 by now. */
137 size_t cbToRead = (size_t)cbSize;
138 pvBuf = RTMemAlloc(cbToRead);
139 if (!pvBuf)
140 {
141 rc = VERR_NO_MEMORY;
142 break;
143 }
144
145 size_t cbRead = 0;
146 rc = RTFileRead(file, pvBuf, cbToRead, &cbRead);
147 if (RT_FAILURE(rc))
148 break;
149
150 rc = RTManifestVerifyFilesBuf(pvBuf, cbRead, paTests, cTests, piFailed);
151 }while (0);
152
153 /* Cleanup */
154 if (pvBuf)
155 RTMemFree(pvBuf);
156
157 RTFileClose(file);
158
159 return rc;
160}
161
162RTR3DECL(int) RTManifestVerifyFiles(const char *pszManifestFile, const char * const *papszFiles, size_t cFiles, size_t *piFailed,
163 PFNRTPROGRESS pfnProgressCallback, void *pvUser)
164{
165 /* Validate input */
166 AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER);
167 AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
168 AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_POINTER);
169
170 int rc = VINF_SUCCESS;
171
172 /* Create our compare list */
173 PRTMANIFESTTEST paFiles = (PRTMANIFESTTEST)RTMemTmpAllocZ(sizeof(RTMANIFESTTEST) * cFiles);
174 if (!paFiles)
175 return VERR_NO_MEMORY;
176
177 RTMANIFESTCALLBACKDATA callback = { pfnProgressCallback, pvUser, cFiles, 0 };
178 /* Fill our compare list */
179 for (size_t i = 0; i < cFiles; ++i)
180 {
181 char *pszDigest;
182 if (pfnProgressCallback)
183 {
184 callback.cCurrentFile = i;
185 rc = RTSha1DigestFromFile(papszFiles[i], &pszDigest, rtSHAProgressCallback, &callback);
186 }
187 else
188 rc = RTSha1DigestFromFile(papszFiles[i], &pszDigest, NULL, NULL);
189 if (RT_FAILURE(rc))
190 break;
191 paFiles[i].pszTestFile = (char*)papszFiles[i];
192 paFiles[i].pszTestDigest = pszDigest;
193 }
194
195 /* Do the verification */
196 if (RT_SUCCESS(rc))
197 rc = RTManifestVerify(pszManifestFile, paFiles, cFiles, piFailed);
198
199 /* Cleanup */
200 for (size_t i = 0; i < cFiles; ++i)
201 {
202 if (paFiles[i].pszTestDigest)
203 RTStrFree((char*)paFiles[i].pszTestDigest);
204 }
205 RTMemTmpFree(paFiles);
206
207 return rc;
208}
209
210RTR3DECL(int) RTManifestWriteFiles(const char *pszManifestFile, RTDIGESTTYPE enmDigestType,
211 const char * const *papszFiles, size_t cFiles,
212 PFNRTPROGRESS pfnProgressCallback, void *pvUser)
213{
214 /* Validate input */
215 AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER);
216 AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
217 AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_POINTER);
218
219 RTFILE file;
220 int rc = RTFileOpen(&file, pszManifestFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL);
221 if (RT_FAILURE(rc))
222 return rc;
223
224 PRTMANIFESTTEST paFiles = 0;
225 void *pvBuf = 0;
226 do
227 {
228 paFiles = (PRTMANIFESTTEST)RTMemAllocZ(sizeof(RTMANIFESTTEST) * cFiles);
229 if (!paFiles)
230 {
231 rc = VERR_NO_MEMORY;
232 break;
233 }
234
235 RTMANIFESTCALLBACKDATA callback = { pfnProgressCallback, pvUser, cFiles, 0 };
236 for (size_t i = 0; i < cFiles; ++i)
237 {
238 paFiles[i].pszTestFile = papszFiles[i];
239 /* Calculate the SHA1 digest of every file */
240 if (pfnProgressCallback)
241 {
242 callback.cCurrentFile = i;
243 rc = RTSha1DigestFromFile(paFiles[i].pszTestFile, (char**)&paFiles[i].pszTestDigest, rtSHAProgressCallback, &callback);
244 }
245 else
246 rc = RTSha1DigestFromFile(paFiles[i].pszTestFile, (char**)&paFiles[i].pszTestDigest, NULL, NULL);
247 if (RT_FAILURE(rc))
248 break;
249 }
250
251 if (RT_SUCCESS(rc))
252 {
253 size_t cbSize = 0;
254 rc = RTManifestWriteFilesBuf(&pvBuf, &cbSize, enmDigestType, paFiles, cFiles);
255 if (RT_FAILURE(rc))
256 break;
257
258 rc = RTFileWrite(file, pvBuf, cbSize, 0);
259 }
260 }while (0);
261
262 RTFileClose(file);
263
264 /* Cleanup */
265 if (pvBuf)
266 RTMemFree(pvBuf);
267 if (paFiles)
268 {
269 for (size_t i = 0; i < cFiles; ++i)
270 if (paFiles[i].pszTestDigest)
271 RTStrFree((char*)paFiles[i].pszTestDigest);
272 RTMemFree(paFiles);
273 }
274
275 /* Delete the manifest file on failure */
276 if (RT_FAILURE(rc))
277 RTFileDelete(pszManifestFile);
278
279 return rc;
280}
281
282
283RTR3DECL(int) RTManifestVerifyDigestType(void const *pvBuf, size_t cbSize, RTDIGESTTYPE *penmDigestType)
284{
285 /* Validate input */
286 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
287 AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER);
288 AssertPtrReturn(penmDigestType, VERR_INVALID_POINTER);
289
290 int rc = VINF_SUCCESS;
291
292 char const *pcBuf = (char *)pvBuf;
293 size_t cbRead = 0;
294 /* Parse the manifest file line by line */
295 for (;;)
296 {
297 if (cbRead >= cbSize)
298 return VERR_MANIFEST_UNSUPPORTED_DIGEST_TYPE;
299
300 size_t cch = rtManifestIndexOfCharInBuf(pcBuf, cbSize - cbRead, '\n') + 1;
301
302 /* Skip empty lines (UNIX/DOS format) */
303 if ( ( cch == 1
304 && pcBuf[0] == '\n')
305 || ( cch == 2
306 && pcBuf[0] == '\r'
307 && pcBuf[1] == '\n'))
308 {
309 pcBuf += cch;
310 cbRead += cch;
311 continue;
312 }
313
314/** @todo r=bird: Missing space check here. */
315 /* Check for the digest algorithm */
316 if ( pcBuf[0] == 'S'
317 && pcBuf[1] == 'H'
318 && pcBuf[2] == 'A'
319 && pcBuf[3] == '1')
320 {
321 *penmDigestType = RTDIGESTTYPE_SHA1;
322 break;
323 }
324 if ( pcBuf[0] == 'S'
325 && pcBuf[1] == 'H'
326 && pcBuf[2] == 'A'
327 && pcBuf[3] == '2'
328 && pcBuf[4] == '5'
329 && pcBuf[5] == '6')
330 {
331 *penmDigestType = RTDIGESTTYPE_SHA256;
332 break;
333 }
334
335 pcBuf += cch;
336 cbRead += cch;
337 }
338
339 return rc;
340}
341
342
343RTR3DECL(int) RTManifestVerifyFilesBuf(void *pvBuf, size_t cbSize, PRTMANIFESTTEST paTests, size_t cTests, size_t *piFailed)
344{
345 /* Validate input */
346 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
347 AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER);
348 AssertPtrReturn(paTests, VERR_INVALID_POINTER);
349 AssertReturn(cTests > 0, VERR_INVALID_PARAMETER);
350 AssertPtrNullReturn(piFailed, VERR_INVALID_POINTER);
351
352 int rc = VINF_SUCCESS;
353
354 PRTMANIFESTFILEENTRY paFiles = (PRTMANIFESTFILEENTRY)RTMemTmpAllocZ(sizeof(RTMANIFESTFILEENTRY) * cTests);
355 if (!paFiles)
356 return VERR_NO_MEMORY;
357
358 /* Fill our compare list */
359 for (size_t i = 0; i < cTests; ++i)
360 paFiles[i].pTestPattern = &paTests[i];
361
362 char *pcBuf = (char*)pvBuf;
363 size_t cbRead = 0;
364 /* Parse the manifest file line by line */
365 for (;;)
366 {
367 if (cbRead >= cbSize)
368 break;
369
370 size_t cch = rtManifestIndexOfCharInBuf(pcBuf, cbSize - cbRead, '\n') + 1;
371
372 /* Skip empty lines (UNIX/DOS format) */
373 if ( ( cch == 1
374 && pcBuf[0] == '\n')
375 || ( cch == 2
376 && pcBuf[0] == '\r'
377 && pcBuf[1] == '\n'))
378 {
379 pcBuf += cch;
380 cbRead += cch;
381 continue;
382 }
383
384 /** @todo r=bird:
385 * -# Better deal with this EOF line platform dependency
386 * -# The SHA1 and SHA256 tests should probably include a blank space check.
387 * -# If there is a specific order to the elements in the string, it would be
388 * good if the delimiter searching checked for it.
389 * -# Deal with filenames containing delimiter characters.
390 */
391
392 /* Check for the digest algorithm */
393 if ( cch < 4
394 || ( !( pcBuf[0] == 'S'
395 && pcBuf[1] == 'H'
396 && pcBuf[2] == 'A'
397 && pcBuf[3] == '1')
398 &&
399 !( pcBuf[0] == 'S'
400 && pcBuf[1] == 'H'
401 && pcBuf[2] == 'A'
402 && pcBuf[3] == '2'
403 && pcBuf[4] == '5'
404 && pcBuf[5] == '6')
405 )
406 )
407 {
408 /* Digest unsupported */
409 rc = VERR_MANIFEST_UNSUPPORTED_DIGEST_TYPE;
410 break;
411 }
412
413 /* Try to find the filename */
414 char *pszNameStart = rtManifestPosOfCharInBuf(pcBuf, cch, '(');
415 if (!pszNameStart)
416 {
417 rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
418 break;
419 }
420 char *pszNameEnd = rtManifestPosOfCharInBuf(pcBuf, cch, ')');
421 if (!pszNameEnd)
422 {
423 rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
424 break;
425 }
426
427 /* Copy the filename part */
428 size_t cchName = pszNameEnd - pszNameStart - 1;
429 char *pszName = (char *)RTMemTmpAlloc(cchName + 1);
430 if (!pszName)
431 {
432 rc = VERR_NO_MEMORY;
433 break;
434 }
435 memcpy(pszName, pszNameStart + 1, cchName);
436 pszName[cchName] = '\0';
437
438 /* Try to find the digest sum */
439 char *pszDigestStart = rtManifestPosOfCharInBuf(pcBuf, cch, '=') + 1;
440 if (!pszDigestStart)
441 {
442 RTMemTmpFree(pszName);
443 rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
444 break;
445 }
446 char *pszDigestEnd = rtManifestPosOfCharInBuf(pcBuf, cch, '\r');
447 if (!pszDigestEnd)
448 pszDigestEnd = rtManifestPosOfCharInBuf(pcBuf, cch, '\n');
449 if (!pszDigestEnd)
450 {
451 RTMemTmpFree(pszName);
452 rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
453 break;
454 }
455 /* Copy the digest part */
456 size_t cchDigest = pszDigestEnd - pszDigestStart - 1;
457 char *pszDigest = (char *)RTMemTmpAlloc(cchDigest + 1);
458 if (!pszDigest)
459 {
460 RTMemTmpFree(pszName);
461 rc = VERR_NO_MEMORY;
462 break;
463 }
464 memcpy(pszDigest, pszDigestStart + 1, cchDigest);
465 pszDigest[cchDigest] = '\0';
466
467 /* Check our file list against the extracted data */
468 bool fFound = false;
469 for (size_t i = 0; i < cTests; ++i)
470 {
471 /** @todo r=bird: Using RTStrStr here looks bogus. */
472 if (RTStrStr(paFiles[i].pTestPattern->pszTestFile, RTStrStrip(pszName)) != NULL)
473 {
474 /* Add the data of the manifest file to the file list */
475 paFiles[i].pszManifestFile = RTStrDup(RTStrStrip(pszName));
476 paFiles[i].pszManifestDigest = RTStrDup(RTStrStrip(pszDigest));
477 fFound = true;
478 break;
479 }
480 }
481 RTMemTmpFree(pszName);
482 RTMemTmpFree(pszDigest);
483 if (!fFound)
484 {
485 /* There have to be an entry in the file list */
486 rc = VERR_MANIFEST_FILE_MISMATCH;
487 break;
488 }
489
490 pcBuf += cch;
491 cbRead += cch;
492 }
493
494 if ( rc == VINF_SUCCESS
495 || rc == VERR_EOF)
496 {
497 rc = VINF_SUCCESS;
498 for (size_t i = 0; i < cTests; ++i)
499 {
500 /* If there is an entry in the file list, which hasn't an
501 * equivalent in the manifest file, its an error. */
502 if ( !paFiles[i].pszManifestFile
503 || !paFiles[i].pszManifestDigest)
504 {
505 rc = VERR_MANIFEST_FILE_MISMATCH;
506 break;
507 }
508
509 /* Do the manifest SHA digest match against the actual digest? */
510 if (RTStrICmp(paFiles[i].pszManifestDigest, paFiles[i].pTestPattern->pszTestDigest))
511 {
512 if (piFailed)
513 *piFailed = i;
514 rc = VERR_MANIFEST_DIGEST_MISMATCH;
515 break;
516 }
517 }
518 }
519
520 /* Cleanup */
521 for (size_t i = 0; i < cTests; ++i)
522 {
523 if (paFiles[i].pszManifestFile)
524 RTStrFree(paFiles[i].pszManifestFile);
525 if (paFiles[i].pszManifestDigest)
526 RTStrFree(paFiles[i].pszManifestDigest);
527 }
528 RTMemTmpFree(paFiles);
529
530 return rc;
531}
532
533RTR3DECL(int) RTManifestWriteFilesBuf(void **ppvBuf, size_t *pcbSize, RTDIGESTTYPE enmDigestType, PRTMANIFESTTEST paFiles, size_t cFiles)
534{
535 /* Validate input */
536 AssertPtrReturn(ppvBuf, VERR_INVALID_POINTER);
537 AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
538 AssertPtrReturn(paFiles, VERR_INVALID_POINTER);
539 AssertReturn(cFiles > 0, VERR_INVALID_PARAMETER);
540
541 const char *pcszDigestType;
542 switch (enmDigestType)
543 {
544 case RTDIGESTTYPE_CRC32: pcszDigestType = "CRC32"; break;
545 case RTDIGESTTYPE_CRC64: pcszDigestType = "CRC64"; break;
546 case RTDIGESTTYPE_MD5: pcszDigestType = "MD5"; break;
547 case RTDIGESTTYPE_SHA1: pcszDigestType = "SHA1"; break;
548 case RTDIGESTTYPE_SHA256: pcszDigestType = "SHA256"; break;
549 default: return VERR_INVALID_PARAMETER;
550 }
551
552 /* Calculate the size necessary for the memory buffer. */
553 size_t cbSize = 0;
554 size_t cbMaxSize = 0;
555 for (size_t i = 0; i < cFiles; ++i)
556 {
557 size_t cbTmp = strlen(RTPathFilename(paFiles[i].pszTestFile))
558 + strlen(paFiles[i].pszTestDigest)
559 + strlen(pcszDigestType)
560 + 6;
561 cbMaxSize = RT_MAX(cbMaxSize, cbTmp);
562 cbSize += cbTmp;
563 }
564
565 /* Create the memory buffer */
566 void *pvBuf = RTMemAlloc(cbSize);
567 if (!pvBuf)
568 return VERR_NO_MEMORY;
569
570 /* Allocate a temporary string buffer. */
571 char *pszTmp = RTStrAlloc(cbMaxSize + 1);
572 if (!pszTmp)
573 {
574 RTMemFree(pvBuf);
575 return VERR_NO_MEMORY;
576 }
577 size_t cbPos = 0;
578
579 for (size_t i = 0; i < cFiles; ++i)
580 {
581 size_t cch = RTStrPrintf(pszTmp, cbMaxSize + 1, "%s (%s)= %s\n", pcszDigestType, RTPathFilename(paFiles[i].pszTestFile), paFiles[i].pszTestDigest);
582 memcpy(&((char*)pvBuf)[cbPos], pszTmp, cch);
583 cbPos += cch;
584 }
585 RTStrFree(pszTmp);
586
587 /* Results */
588 *ppvBuf = pvBuf;
589 *pcbSize = cbSize;
590
591 return VINF_SUCCESS;
592}
593
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette