diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Runtime/common/checksum/manifest.cpp | 583 |
1 files changed, 583 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/checksum/manifest.cpp b/src/VBox/Runtime/common/checksum/manifest.cpp new file mode 100644 index 00000000..498365fa --- /dev/null +++ b/src/VBox/Runtime/common/checksum/manifest.cpp @@ -0,0 +1,583 @@ +/* $Id: manifest.cpp $ */ +/** @file + * IPRT - Manifest file handling, old style - deprecated. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/manifest.h> + +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/sha.h> +#include <iprt/stream.h> +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Internal per file structure used by RTManifestVerify + */ +typedef struct RTMANIFESTFILEENTRY +{ + char *pszManifestFile; + char *pszManifestDigest; + PRTMANIFESTTEST pTestPattern; +} RTMANIFESTFILEENTRY; +typedef RTMANIFESTFILEENTRY* PRTMANIFESTFILEENTRY; + +/** + * Internal structure used for the progress callback + */ +typedef struct RTMANIFESTCALLBACKDATA +{ + PFNRTPROGRESS pfnProgressCallback; + void *pvUser; + size_t cMaxFiles; + size_t cCurrentFile; +} RTMANIFESTCALLBACKDATA; +typedef RTMANIFESTCALLBACKDATA* PRTMANIFESTCALLBACKDATA; + + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +DECLINLINE(char *) rtManifestPosOfCharInBuf(char const *pv, size_t cb, char c) +{ + char *pb = (char *)pv; + for (; cb; --cb, ++pb) + if (RT_UNLIKELY(*pb == c)) + return pb; + return NULL; +} + +DECLINLINE(size_t) rtManifestIndexOfCharInBuf(char const *pv, size_t cb, char c) +{ + char const *pb = (char const *)pv; + for (size_t i=0; i < cb; ++i, ++pb) + if (RT_UNLIKELY(*pb == c)) + return i; + return cb; +} + +static DECLCALLBACK(int) rtSHAProgressCallback(unsigned uPercent, void *pvUser) +{ + PRTMANIFESTCALLBACKDATA pData = (PRTMANIFESTCALLBACKDATA)pvUser; + return pData->pfnProgressCallback((unsigned)( (uPercent + (float)pData->cCurrentFile * 100.0) + / (float)pData->cMaxFiles), + pData->pvUser); +} + + +/******************************************************************************* +* Public functions +*******************************************************************************/ + +RTR3DECL(int) RTManifestVerify(const char *pszManifestFile, PRTMANIFESTTEST paTests, size_t cTests, size_t *piFailed) +{ + /* Validate input */ + AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER); + + /* Open the manifest file */ + RTFILE file; + int rc = RTFileOpen(&file, pszManifestFile, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return rc; + + void *pvBuf = 0; + do + { + uint64_t cbSize; + rc = RTFileGetSize(file, &cbSize); + if (RT_FAILURE(rc)) + break; + + /* Cast down for the case size_t < uint64_t. This isn't really correct, + but we consider manifest files bigger than size_t as not supported + by now. */ + size_t cbToRead = (size_t)cbSize; + pvBuf = RTMemAlloc(cbToRead); + if (!pvBuf) + { + rc = VERR_NO_MEMORY; + break; + } + + size_t cbRead = 0; + rc = RTFileRead(file, pvBuf, cbToRead, &cbRead); + if (RT_FAILURE(rc)) + break; + + rc = RTManifestVerifyFilesBuf(pvBuf, cbRead, paTests, cTests, piFailed); + }while (0); + + /* Cleanup */ + if (pvBuf) + RTMemFree(pvBuf); + + RTFileClose(file); + + return rc; +} + +RTR3DECL(int) RTManifestVerifyFiles(const char *pszManifestFile, const char * const *papszFiles, size_t cFiles, size_t *piFailed, + PFNRTPROGRESS pfnProgressCallback, void *pvUser) +{ + /* Validate input */ + AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER); + AssertPtrReturn(papszFiles, VERR_INVALID_POINTER); + AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + /* Create our compare list */ + PRTMANIFESTTEST paFiles = (PRTMANIFESTTEST)RTMemTmpAllocZ(sizeof(RTMANIFESTTEST) * cFiles); + if (!paFiles) + return VERR_NO_MEMORY; + + RTMANIFESTCALLBACKDATA callback = { pfnProgressCallback, pvUser, cFiles, 0 }; + /* Fill our compare list */ + for (size_t i = 0; i < cFiles; ++i) + { + char *pszDigest; + if (pfnProgressCallback) + { + callback.cCurrentFile = i; + rc = RTSha1DigestFromFile(papszFiles[i], &pszDigest, rtSHAProgressCallback, &callback); + } + else + rc = RTSha1DigestFromFile(papszFiles[i], &pszDigest, NULL, NULL); + if (RT_FAILURE(rc)) + break; + paFiles[i].pszTestFile = (char*)papszFiles[i]; + paFiles[i].pszTestDigest = pszDigest; + } + + /* Do the verification */ + if (RT_SUCCESS(rc)) + rc = RTManifestVerify(pszManifestFile, paFiles, cFiles, piFailed); + + /* Cleanup */ + for (size_t i = 0; i < cFiles; ++i) + { + if (paFiles[i].pszTestDigest) + RTStrFree((char*)paFiles[i].pszTestDigest); + } + RTMemTmpFree(paFiles); + + return rc; +} + +RTR3DECL(int) RTManifestWriteFiles(const char *pszManifestFile, RTDIGESTTYPE enmDigestType, + const char * const *papszFiles, size_t cFiles, + PFNRTPROGRESS pfnProgressCallback, void *pvUser) +{ + /* Validate input */ + AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER); + AssertPtrReturn(papszFiles, VERR_INVALID_POINTER); + AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_POINTER); + + RTFILE file; + int rc = RTFileOpen(&file, pszManifestFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL); + if (RT_FAILURE(rc)) + return rc; + + PRTMANIFESTTEST paFiles = 0; + void *pvBuf = 0; + do + { + paFiles = (PRTMANIFESTTEST)RTMemAllocZ(sizeof(RTMANIFESTTEST) * cFiles); + if (!paFiles) + { + rc = VERR_NO_MEMORY; + break; + } + + RTMANIFESTCALLBACKDATA callback = { pfnProgressCallback, pvUser, cFiles, 0 }; + for (size_t i = 0; i < cFiles; ++i) + { + paFiles[i].pszTestFile = papszFiles[i]; + /* Calculate the SHA1 digest of every file */ + if (pfnProgressCallback) + { + callback.cCurrentFile = i; + rc = RTSha1DigestFromFile(paFiles[i].pszTestFile, (char**)&paFiles[i].pszTestDigest, rtSHAProgressCallback, &callback); + } + else + rc = RTSha1DigestFromFile(paFiles[i].pszTestFile, (char**)&paFiles[i].pszTestDigest, NULL, NULL); + if (RT_FAILURE(rc)) + break; + } + + if (RT_SUCCESS(rc)) + { + size_t cbSize = 0; + rc = RTManifestWriteFilesBuf(&pvBuf, &cbSize, enmDigestType, paFiles, cFiles); + if (RT_FAILURE(rc)) + break; + + rc = RTFileWrite(file, pvBuf, cbSize, 0); + } + }while (0); + + RTFileClose(file); + + /* Cleanup */ + if (pvBuf) + RTMemFree(pvBuf); + if (paFiles) + { + for (size_t i = 0; i < cFiles; ++i) + if (paFiles[i].pszTestDigest) + RTStrFree((char*)paFiles[i].pszTestDigest); + RTMemFree(paFiles); + } + + /* Delete the manifest file on failure */ + if (RT_FAILURE(rc)) + RTFileDelete(pszManifestFile); + + return rc; +} + + +RTR3DECL(int) RTManifestVerifyDigestType(void const *pvBuf, size_t cbSize, RTDIGESTTYPE *penmDigestType) +{ + /* Validate input */ + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(penmDigestType, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + char const *pcBuf = (char *)pvBuf; + size_t cbRead = 0; + /* Parse the manifest file line by line */ + for (;;) + { + if (cbRead >= cbSize) + return VERR_MANIFEST_UNSUPPORTED_DIGEST_TYPE; + + size_t cch = rtManifestIndexOfCharInBuf(pcBuf, cbSize - cbRead, '\n') + 1; + + /* Skip empty lines (UNIX/DOS format) */ + if ( ( cch == 1 + && pcBuf[0] == '\n') + || ( cch == 2 + && pcBuf[0] == '\r' + && pcBuf[1] == '\n')) + { + pcBuf += cch; + cbRead += cch; + continue; + } + +/** @todo r=bird: Missing space check here. */ + /* Check for the digest algorithm */ + if ( pcBuf[0] == 'S' + && pcBuf[1] == 'H' + && pcBuf[2] == 'A' + && pcBuf[3] == '1') + { + *penmDigestType = RTDIGESTTYPE_SHA1; + break; + } + if ( pcBuf[0] == 'S' + && pcBuf[1] == 'H' + && pcBuf[2] == 'A' + && pcBuf[3] == '2' + && pcBuf[4] == '5' + && pcBuf[5] == '6') + { + *penmDigestType = RTDIGESTTYPE_SHA256; + break; + } + + pcBuf += cch; + cbRead += cch; + } + + return rc; +} + + +RTR3DECL(int) RTManifestVerifyFilesBuf(void *pvBuf, size_t cbSize, PRTMANIFESTTEST paTests, size_t cTests, size_t *piFailed) +{ + /* Validate input */ + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(paTests, VERR_INVALID_POINTER); + AssertReturn(cTests > 0, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(piFailed, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + PRTMANIFESTFILEENTRY paFiles = (PRTMANIFESTFILEENTRY)RTMemTmpAllocZ(sizeof(RTMANIFESTFILEENTRY) * cTests); + if (!paFiles) + return VERR_NO_MEMORY; + + /* Fill our compare list */ + for (size_t i = 0; i < cTests; ++i) + paFiles[i].pTestPattern = &paTests[i]; + + char *pcBuf = (char*)pvBuf; + size_t cbRead = 0; + /* Parse the manifest file line by line */ + for (;;) + { + if (cbRead >= cbSize) + break; + + size_t cch = rtManifestIndexOfCharInBuf(pcBuf, cbSize - cbRead, '\n') + 1; + + /* Skip empty lines (UNIX/DOS format) */ + if ( ( cch == 1 + && pcBuf[0] == '\n') + || ( cch == 2 + && pcBuf[0] == '\r' + && pcBuf[1] == '\n')) + { + pcBuf += cch; + cbRead += cch; + continue; + } + + /** @todo r=bird: + * -# Better deal with this EOF line platform dependency + * -# The SHA1 and SHA256 tests should probably include a blank space check. + * -# If there is a specific order to the elements in the string, it would be + * good if the delimiter searching checked for it. + * -# Deal with filenames containing delimiter characters. + */ + + /* Check for the digest algorithm */ + if ( cch < 4 + || ( !( pcBuf[0] == 'S' + && pcBuf[1] == 'H' + && pcBuf[2] == 'A' + && pcBuf[3] == '1') + && + !( pcBuf[0] == 'S' + && pcBuf[1] == 'H' + && pcBuf[2] == 'A' + && pcBuf[3] == '2' + && pcBuf[4] == '5' + && pcBuf[5] == '6') + ) + ) + { + /* Digest unsupported */ + rc = VERR_MANIFEST_UNSUPPORTED_DIGEST_TYPE; + break; + } + + /* Try to find the filename */ + char *pszNameStart = rtManifestPosOfCharInBuf(pcBuf, cch, '('); + if (!pszNameStart) + { + rc = VERR_MANIFEST_WRONG_FILE_FORMAT; + break; + } + char *pszNameEnd = rtManifestPosOfCharInBuf(pcBuf, cch, ')'); + if (!pszNameEnd) + { + rc = VERR_MANIFEST_WRONG_FILE_FORMAT; + break; + } + + /* Copy the filename part */ + size_t cchName = pszNameEnd - pszNameStart - 1; + char *pszName = (char *)RTMemTmpAlloc(cchName + 1); + if (!pszName) + { + rc = VERR_NO_MEMORY; + break; + } + memcpy(pszName, pszNameStart + 1, cchName); + pszName[cchName] = '\0'; + + /* Try to find the digest sum */ + char *pszDigestStart = rtManifestPosOfCharInBuf(pcBuf, cch, '=') + 1; + if (!pszDigestStart) + { + RTMemTmpFree(pszName); + rc = VERR_MANIFEST_WRONG_FILE_FORMAT; + break; + } + char *pszDigestEnd = rtManifestPosOfCharInBuf(pcBuf, cch, '\r'); + if (!pszDigestEnd) + pszDigestEnd = rtManifestPosOfCharInBuf(pcBuf, cch, '\n'); + if (!pszDigestEnd) + { + RTMemTmpFree(pszName); + rc = VERR_MANIFEST_WRONG_FILE_FORMAT; + break; + } + /* Copy the digest part */ + size_t cchDigest = pszDigestEnd - pszDigestStart - 1; + char *pszDigest = (char *)RTMemTmpAlloc(cchDigest + 1); + if (!pszDigest) + { + RTMemTmpFree(pszName); + rc = VERR_NO_MEMORY; + break; + } + memcpy(pszDigest, pszDigestStart + 1, cchDigest); + pszDigest[cchDigest] = '\0'; + + /* Check our file list against the extracted data */ + bool fFound = false; + for (size_t i = 0; i < cTests; ++i) + { + /** @todo r=bird: Using RTStrStr here looks bogus. */ + if (RTStrStr(paFiles[i].pTestPattern->pszTestFile, RTStrStrip(pszName)) != NULL) + { + /* Add the data of the manifest file to the file list */ + paFiles[i].pszManifestFile = RTStrDup(RTStrStrip(pszName)); + paFiles[i].pszManifestDigest = RTStrDup(RTStrStrip(pszDigest)); + fFound = true; + break; + } + } + RTMemTmpFree(pszName); + RTMemTmpFree(pszDigest); + if (!fFound) + { + /* There have to be an entry in the file list */ + rc = VERR_MANIFEST_FILE_MISMATCH; + break; + } + + pcBuf += cch; + cbRead += cch; + } + + if ( rc == VINF_SUCCESS + || rc == VERR_EOF) + { + rc = VINF_SUCCESS; + for (size_t i = 0; i < cTests; ++i) + { + /* If there is an entry in the file list, which hasn't an + * equivalent in the manifest file, its an error. */ + if ( !paFiles[i].pszManifestFile + || !paFiles[i].pszManifestDigest) + { + rc = VERR_MANIFEST_FILE_MISMATCH; + break; + } + + /* Do the manifest SHA digest match against the actual digest? */ + if (RTStrICmp(paFiles[i].pszManifestDigest, paFiles[i].pTestPattern->pszTestDigest)) + { + if (piFailed) + *piFailed = i; + rc = VERR_MANIFEST_DIGEST_MISMATCH; + break; + } + } + } + + /* Cleanup */ + for (size_t i = 0; i < cTests; ++i) + { + if (paFiles[i].pszManifestFile) + RTStrFree(paFiles[i].pszManifestFile); + if (paFiles[i].pszManifestDigest) + RTStrFree(paFiles[i].pszManifestDigest); + } + RTMemTmpFree(paFiles); + + return rc; +} + +RTR3DECL(int) RTManifestWriteFilesBuf(void **ppvBuf, size_t *pcbSize, RTDIGESTTYPE enmDigestType, PRTMANIFESTTEST paFiles, size_t cFiles) +{ + /* Validate input */ + AssertPtrReturn(ppvBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pcbSize, VERR_INVALID_POINTER); + AssertPtrReturn(paFiles, VERR_INVALID_POINTER); + AssertReturn(cFiles > 0, VERR_INVALID_PARAMETER); + + const char *pcszDigestType; + switch (enmDigestType) + { + case RTDIGESTTYPE_CRC32: pcszDigestType = "CRC32"; break; + case RTDIGESTTYPE_CRC64: pcszDigestType = "CRC64"; break; + case RTDIGESTTYPE_MD5: pcszDigestType = "MD5"; break; + case RTDIGESTTYPE_SHA1: pcszDigestType = "SHA1"; break; + case RTDIGESTTYPE_SHA256: pcszDigestType = "SHA256"; break; + default: return VERR_INVALID_PARAMETER; + } + + /* Calculate the size necessary for the memory buffer. */ + size_t cbSize = 0; + size_t cbMaxSize = 0; + for (size_t i = 0; i < cFiles; ++i) + { + size_t cbTmp = strlen(RTPathFilename(paFiles[i].pszTestFile)) + + strlen(paFiles[i].pszTestDigest) + + strlen(pcszDigestType) + + 6; + cbMaxSize = RT_MAX(cbMaxSize, cbTmp); + cbSize += cbTmp; + } + + /* Create the memory buffer */ + void *pvBuf = RTMemAlloc(cbSize); + if (!pvBuf) + return VERR_NO_MEMORY; + + /* Allocate a temporary string buffer. */ + char *pszTmp = RTStrAlloc(cbMaxSize + 1); + if (!pszTmp) + { + RTMemFree(pvBuf); + return VERR_NO_MEMORY; + } + size_t cbPos = 0; + + for (size_t i = 0; i < cFiles; ++i) + { + size_t cch = RTStrPrintf(pszTmp, cbMaxSize + 1, "%s (%s)= %s\n", pcszDigestType, RTPathFilename(paFiles[i].pszTestFile), paFiles[i].pszTestDigest); + memcpy(&((char*)pvBuf)[cbPos], pszTmp, cch); + cbPos += cch; + } + RTStrFree(pszTmp); + + /* Results */ + *ppvBuf = pvBuf; + *pcbSize = cbSize; + + return VINF_SUCCESS; +} + |