VirtualBox

source: vbox/trunk/src/VBox/Additions/WINNT/Installer/NSISTool/VBoxNSISTool.cpp@ 107269

Last change on this file since 107269 was 107269, checked in by vboxsync, 5 weeks ago

Windows Additions: Added a new tool VBoxNSISTool for verifying + repairing NSIS installer files, only used at build time. This is necessary for getting the CRC32 right after we've built the NSIS installer and set the version via VBoxSetPeVersion afterwards, which renders the initial checksum within the installer invalid and thus prevents starting the installer. bugref:10761

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 12.0 KB
Line 
1/* $Id: VBoxNSISTool.cpp 107269 2024-12-10 08:20:15Z vboxsync $ */
2/** @file
3 * VBoxNSISTool - Utility program for NSIS-based tasks.
4 */
5
6/*
7 * Copyright (C) 2024 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 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <iprt/assert.h>
33#include <iprt/crc.h>
34#include <iprt/err.h>
35#include <iprt/file.h>
36#include <iprt/initterm.h>
37#include <iprt/message.h>
38#include <iprt/stream.h>
39#include <iprt/string.h>
40
41
42/*********************************************************************************************************************************
43* Defines *
44*********************************************************************************************************************************/
45
46/** Taken from NSIS 3.10. */
47#pragma pack(1)
48typedef struct
49{
50 uint32_t flags; // FH_FLAGS_*
51 uint32_t siginfo; // FH_SIG
52
53 uint32_t nsinst[3]; // FH_INT1,FH_INT2,FH_INT3
54
55 // these point to the header+sections+entries+stringtable in the datablock
56 uint32_t length_of_header;
57
58 // this specifies the length of all the data (including the firstheader and CRC)
59 uint32_t length_of_all_following_data;
60} firstheader;
61#pragma pack()
62
63/** Taken from NSIS 3.10. */
64#define FH_SIG 0xDEADBEEF
65#define FH_INT1 0x6C6C754E
66#define FH_INT2 0x74666F73
67#define FH_INT3 0x74736E49
68
69/** Size of a NSIS block (in bytes). */
70#define VBOX_NSIS_BLOCK_SIZE_BYTES 512
71
72/**
73 * Structure for keeping NSIS installer file data.
74 */
75typedef struct VBOXNSISFILE
76{
77 /** File handle of NSIS installer. */
78 RTFILE hFile;
79 /** File size (in bytes).
80 * Set to 0 if not set (yet). */
81 size_t cbFile;
82 /** Header structure, taken from NSIS. */
83 firstheader FirstHdr;
84 /** Absolute file offset (in bytes) of \a FirstHdr.
85 * Set to UINT64_MAX if not set (yet). */
86 size_t offHdr;
87 /** Absolute file offset (in bytes) of CRC32 to read/write.
88 * Set to UINT64_MAX if not set (yet). */
89 size_t offCRC32;
90 /** Currente CRC32 checksum being used.
91 * Set to 0 if not initialized. */
92 uint32_t uCRC32;
93 /** Whether the installer file is considered being valid or not. */
94 bool fVerified;
95} VBOXNSISFILE;
96/** Pointer to a structure for keeping NSIS installer file data. */
97typedef VBOXNSISFILE *PVBOXNSISFILE;
98AssertCompileMemberSize(VBOXNSISFILE, uCRC32, 4);
99
100/**
101 * Calculates the CRC32 of a given NSIS installer file.
102 *
103 * @returns VBox status code.
104 * @param pNSISFile NSIS installer file to calculate CRC32 for.
105 * @param puCRC32 Where to return the calculated CRC32 on success.
106 */
107static int vboxNSISCRC32Calc(PVBOXNSISFILE pNSISFile, uint32_t *puCRC32)
108{
109 AssertPtrReturn(puCRC32, VERR_INVALID_POINTER);
110
111 AssertReturn(pNSISFile->FirstHdr.length_of_all_following_data, VERR_INVALID_PARAMETER);
112 AssertReturn(pNSISFile->offHdr % VBOX_NSIS_BLOCK_SIZE_BYTES == 0, VERR_INVALID_PARAMETER);
113
114 size_t const offRead = VBOX_NSIS_BLOCK_SIZE_BYTES;
115
116 int rc = RTFileSeek(pNSISFile->hFile, offRead, RTFILE_SEEK_BEGIN, NULL);
117 if (RT_FAILURE(rc))
118 return rc;
119
120 size_t cbToRead = pNSISFile->offHdr
121 + pNSISFile->FirstHdr.length_of_all_following_data
122 - sizeof(pNSISFile->uCRC32)
123 - VBOX_NSIS_BLOCK_SIZE_BYTES;
124 Assert(offRead + cbToRead <= pNSISFile->cbFile);
125
126 uint32_t uCRC32 = RTCrc32Start();
127
128 uint8_t abBuf[_64K];
129 while (cbToRead)
130 {
131 size_t cbRead = 0;
132 rc = RTFileRead(pNSISFile->hFile, abBuf, RT_MIN(cbToRead, sizeof(abBuf)), &cbRead);
133 if (RT_FAILURE(rc))
134 break;
135
136 uCRC32 = RTCrc32Process(uCRC32, abBuf, cbRead);
137
138 AssertBreakStmt(cbToRead >= cbRead, rc = VERR_INVALID_PARAMETER);
139 cbToRead -= cbRead;
140 }
141
142 if (RT_SUCCESS(rc))
143 {
144 Assert(cbToRead == 0);
145 *puCRC32 = RTCrc32Finish(uCRC32);
146 }
147
148 return rc;
149}
150
151/**
152 * Writes the CRC32 to the NSIS installer file.
153 *
154 * @returns VBox status code.
155 * @param pNSISFile NSIS installer file to write CRC32 for.
156 * @param uCRC32 CRC32 to write.
157 */
158static int vboxNSISToolCRCWrite(PVBOXNSISFILE pNSISFile, uint32_t uCRC32)
159{
160 AssertReturn(pNSISFile->offCRC32 != UINT64_MAX, VERR_WRONG_ORDER);
161
162 int rc = RTFileSeek(pNSISFile->hFile, pNSISFile->offCRC32, RTFILE_SEEK_BEGIN, NULL);
163 if (RT_FAILURE(rc))
164 return rc;
165
166 return RTFileWrite(pNSISFile->hFile, &uCRC32, sizeof(uCRC32), NULL);
167}
168
169/**
170 * Verifies an NSIS installer file.
171 *
172 * @returns VBox status code.
173 * @retval VINF_SUCCESS if verification successful, error if not.
174 * @param pNSISFile NSIS installer file to verify.
175 */
176static int vboxNSISToolVerify(PVBOXNSISFILE pNSISFile)
177{
178 AssertReturn(pNSISFile->cbFile, VERR_WRONG_ORDER);
179
180 if (pNSISFile->fVerified) /* Already verified? Bail out. */
181 return VINF_SUCCESS;
182
183 int rc = VERR_INVALID_EXE_SIGNATURE;
184
185 /*
186 * Do a little bit of verification first.
187 */
188 size_t i = 0;
189 for (; i < pNSISFile->cbFile / VBOX_NSIS_BLOCK_SIZE_BYTES; i++)
190 {
191 rc = RTFileSeek(pNSISFile->hFile, i * VBOX_NSIS_BLOCK_SIZE_BYTES, RTFILE_SEEK_BEGIN, NULL);
192 if (RT_FAILURE(rc))
193 break;
194
195 RT_ZERO(pNSISFile->FirstHdr);
196
197 size_t cbRead;
198 rc = RTFileRead(pNSISFile->hFile, &pNSISFile->FirstHdr, sizeof(pNSISFile->FirstHdr), &cbRead);
199 if (cbRead != sizeof(pNSISFile->FirstHdr))
200 {
201 rc = VERR_INVALID_EXE_SIGNATURE;
202 break;
203 }
204
205 if ( cbRead == sizeof(pNSISFile->FirstHdr)
206 && pNSISFile->FirstHdr.siginfo == FH_SIG
207 && pNSISFile->FirstHdr.nsinst[0] == FH_INT1
208 && pNSISFile->FirstHdr.nsinst[1] == FH_INT2
209 && pNSISFile->FirstHdr.nsinst[2] == FH_INT3
210 && pNSISFile->FirstHdr.length_of_header
211 && pNSISFile->FirstHdr.length_of_all_following_data)
212 {
213 rc = VINF_SUCCESS;
214 break;
215 }
216 }
217
218 if (RT_FAILURE(rc))
219 {
220 RTMsgError("NSIS Header invalid / corrupt");
221 return rc;
222 }
223
224 pNSISFile->offHdr = i * VBOX_NSIS_BLOCK_SIZE_BYTES;
225 AssertReturn(pNSISFile->offHdr >= sizeof(pNSISFile->uCRC32), VERR_INVALID_EXE_SIGNATURE);
226 AssertReturn(pNSISFile->offHdr % VBOX_NSIS_BLOCK_SIZE_BYTES == 0, VERR_INVALID_EXE_SIGNATURE);
227 pNSISFile->offCRC32 = pNSISFile->offHdr + pNSISFile->FirstHdr.length_of_all_following_data - sizeof(pNSISFile->uCRC32);
228
229 /*
230 * Get the stored CRC checksum.
231 */
232 uint32_t u32CRCFile = 0;
233 rc = RTFileSeek(pNSISFile->hFile, pNSISFile->offCRC32, RTFILE_SEEK_BEGIN, NULL);
234 if (RT_FAILURE(rc))
235 return rc;
236 rc = RTFileRead(pNSISFile->hFile, &u32CRCFile, sizeof(u32CRCFile), NULL);
237 if (RT_FAILURE(rc))
238 return rc;
239
240 /*
241 * Calculate the checksum ourselves.
242 *
243 * Note! We store the calculated checksum in our file context so that we can skip calculating it
244 * again when writing (patching) the checksum.
245 */
246 rc = vboxNSISCRC32Calc(pNSISFile, &pNSISFile->uCRC32);
247 if (RT_SUCCESS(rc))
248 {
249 if (pNSISFile->uCRC32 == u32CRCFile)
250 {
251 pNSISFile->fVerified = true;
252 }
253 else
254 {
255 RTMsgInfo("Warning: Checksums do not match (expected %#x, got %#x)\n", pNSISFile->uCRC32, u32CRCFile);
256 rc = VERR_INVALID_PARAMETER;
257 }
258 }
259 else
260 RTMsgError("Error calculating checksum, rc=%Rrc\n", rc);
261
262 return rc;
263}
264
265/**
266 * Perform a repair of a given NSIS installer file.
267 *
268 * @returns VBox status code.
269 * @param pNSISFile NSIS installer file to perform repair for.
270 */
271static int vboxNSISToolRepair(PVBOXNSISFILE pNSISFile)
272{
273 if (pNSISFile->fVerified) /* Successfully verified? Skip repair. */
274 return VINF_SUCCESS;
275
276 /* Write the CRC32 we already calculated when verifying the file in a former step. */
277 return vboxNSISToolCRCWrite(pNSISFile, pNSISFile->uCRC32);
278}
279
280/**
281 * Prints the syntax help.
282 *
283 * @returns RTEXITCODE_SYNTAX.
284 * @param argc Number of arguments in \a argv.
285 * @param argv Argument vector.
286 */
287static RTEXITCODE printHelp(int argc, char **argv)
288{
289 RT_NOREF(argc);
290
291 RTPrintf("Syntax:\n\n");
292 RTPrintf(" %s repair <NSIS installer file>\n", argv[0]);
293 RTPrintf(" %s verify <NSIS installer file>\n", argv[0]);
294
295 return RTEXITCODE_SYNTAX;
296}
297
298int main(int argc, char **argv)
299{
300 int rc = RTR3InitExe(argc, &argv, 0);
301 if (RT_FAILURE(rc))
302 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Initializing IPRT failed with %Rrc", rc);
303
304 /* Keep it as simple as possible here currently. */
305 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
306 char const *pszCmd = NULL;
307
308 if (argc < 2)
309 {
310 rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified!\n");
311 }
312 else
313 {
314 pszCmd = argv[1];
315
316 if ( RTStrICmp(pszCmd, "repair")
317 && RTStrICmp(pszCmd, "verify"))
318 {
319 rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid command specified!\n");
320 }
321 if (argc < 3)
322 rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "No NSIS installer file specified!");
323 }
324
325 if (rcExit == RTEXITCODE_SYNTAX)
326 return printHelp(argc, argv);
327
328 char const *pszFile = argv[2];
329
330 VBOXNSISFILE File;
331 RT_ZERO(File);
332 File.offHdr = UINT64_MAX;
333 File.offCRC32 = UINT64_MAX;
334
335 rc = RTFileOpen(&File.hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE);
336 if (RT_FAILURE(rc))
337 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Could not open file '%s' (%Rrc)", pszFile, rc);
338
339 rc = RTFileQuerySize(File.hFile, &File.cbFile);
340 if (RT_SUCCESS(rc) && File.cbFile)
341 {
342 RTMsgInfo("Verifying file '%s' (%zu bytes) ...\n", pszFile, File.cbFile);
343
344 rc = vboxNSISToolVerify(&File);
345 if (RT_SUCCESS(rc))
346 {
347 if (!RTStrICmp(pszCmd, "verify"))
348 RTMsgInfo("Verification successful\n");
349 else
350 RTMsgInfo("File is valid, no repair necessary\n");
351 }
352 else
353 {
354 if (rc == VERR_EOF)
355 RTMsgError("Installer contains no data (empty)?!\n");
356 else
357 {
358 if (!RTStrICmp(pszCmd, "repair"))
359 {
360 RTMsgInfo("Verification failed, repairing ...\n");
361 rc = vboxNSISToolRepair(&File);
362 if (RT_SUCCESS(rc))
363 RTMsgInfo("Repair successful\n");
364 }
365 else
366 RTMsgError("Verification failed\n");
367 }
368 }
369 }
370 else if (RT_FAILURE(rc))
371 RTMsgError("Could not query file size (%Rrc)", rc);
372 else
373 {
374 RTMsgError("File size invalid (%zu bytes)", File.cbFile);
375 rc = VERR_INVALID_EXE_SIGNATURE;
376 }
377
378 if (RT_FAILURE(rc))
379 RTMsgError("Failed with %Rrc", rc);
380
381 RTFileClose(File.hFile);
382 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
383}
384
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