VirtualBox

source: vbox/trunk/src/VBox/HostDrivers/Support/SUPR3HardenedVerify.cpp@ 60480

Last change on this file since 60480 was 59747, checked in by vboxsync, 9 years ago

iprt/asm.h: Cleaned up the ASMMemIsAll8/U32 mess and implmeneted the former in assembly. (Found inverted usage due to bad naming in copyUtf8Block, but it is fortunately an unused method.) Replaces the complicated ASMBitFirstSet based scanning in RTSgBufIsZero with a simple call to the new ASMMemIsZero function.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 71.1 KB
Line 
1/* $Id: SUPR3HardenedVerify.cpp 59747 2016-02-19 23:18:18Z vboxsync $ */
2/** @file
3 * VirtualBox Support Library - Verification of Hardened Installation.
4 */
5
6/*
7 * Copyright (C) 2006-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#if defined(RT_OS_OS2)
32# define INCL_BASE
33# define INCL_ERRORS
34# include <os2.h>
35# include <stdio.h>
36# include <stdlib.h>
37# include <unistd.h>
38# include <sys/fcntl.h>
39# include <sys/errno.h>
40# include <sys/syslimits.h>
41
42#elif defined(RT_OS_WINDOWS)
43# include <iprt/nt/nt-and-windows.h>
44# ifndef IN_SUP_HARDENED_R3
45# include <stdio.h>
46# endif
47
48#else /* UNIXes */
49# include <sys/types.h>
50# include <stdio.h>
51# include <stdlib.h>
52# include <dirent.h>
53# include <dlfcn.h>
54# include <fcntl.h>
55# include <limits.h>
56# include <errno.h>
57# include <unistd.h>
58# include <sys/stat.h>
59# include <sys/time.h>
60# include <sys/fcntl.h>
61# include <pwd.h>
62# ifdef RT_OS_DARWIN
63# include <mach-o/dyld.h>
64# endif
65
66#endif
67
68#include <VBox/sup.h>
69#include <VBox/err.h>
70#include <iprt/asm.h>
71#include <iprt/ctype.h>
72#include <iprt/param.h>
73#include <iprt/path.h>
74#include <iprt/string.h>
75
76#include "SUPLibInternal.h"
77#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_HARDENING)
78# define SUPHNTVI_NO_NT_STUFF
79# include "win/SUPHardenedVerify-win.h"
80#endif
81
82
83/*********************************************************************************************************************************
84* Defined Constants And Macros *
85*********************************************************************************************************************************/
86/** The max path length acceptable for a trusted path. */
87#define SUPR3HARDENED_MAX_PATH 260U
88
89#ifdef RT_OS_SOLARIS
90# define dirfd(d) ((d)->d_fd)
91#endif
92
93/** Compare table file names with externally supplied names. */
94#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
95# define SUP_COMP_FILENAME RTStrICmp
96#else
97# define SUP_COMP_FILENAME suplibHardenedStrCmp
98#endif
99
100
101/*********************************************************************************************************************************
102* Global Variables *
103*********************************************************************************************************************************/
104/**
105 * The files that gets verified.
106 *
107 * @todo This needs reviewing against the linux packages.
108 * @todo The excessive use of kSupID_AppSharedLib needs to be reviewed at some point. For
109 * the time being we're building the linux packages with SharedLib pointing to
110 * AppPrivArch (lazy bird).
111 *
112 * @remarks If you add executables here, you might need to update
113 * g_apszSupNtVpAllowedVmExes in SUPHardenedVerifyProcess-win.cpp.
114 */
115static SUPINSTFILE const g_aSupInstallFiles[] =
116{
117 /* type, dir, fOpt, "pszFile" */
118 /* ---------------------------------------------------------------------- */
119 { kSupIFT_Dll, kSupID_AppPrivArch, false, "VMMR0.r0" },
120 { kSupIFT_Dll, kSupID_AppPrivArch, false, "VBoxDDR0.r0" },
121 { kSupIFT_Dll, kSupID_AppPrivArch, false, "VBoxDD2R0.r0" },
122
123#ifdef VBOX_WITH_RAW_MODE
124 { kSupIFT_Rc, kSupID_AppPrivArch, false, "VMMRC.rc" },
125 { kSupIFT_Rc, kSupID_AppPrivArch, false, "VBoxDDRC.rc" },
126 { kSupIFT_Rc, kSupID_AppPrivArch, false, "VBoxDD2RC.rc" },
127#endif
128
129 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxRT" SUPLIB_DLL_SUFF },
130 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxVMM" SUPLIB_DLL_SUFF },
131 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxREM" SUPLIB_DLL_SUFF },
132#if HC_ARCH_BITS == 32
133 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxREM32" SUPLIB_DLL_SUFF },
134 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxREM64" SUPLIB_DLL_SUFF },
135#endif
136 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxDD" SUPLIB_DLL_SUFF },
137 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxDD2" SUPLIB_DLL_SUFF },
138 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxDDU" SUPLIB_DLL_SUFF },
139 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxVMMPreload" SUPLIB_EXE_SUFF },
140 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxVMMPreload" SUPLIB_DLL_SUFF },
141
142//#ifdef VBOX_WITH_DEBUGGER_GUI
143 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxDbg" SUPLIB_DLL_SUFF },
144 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxDbg3" SUPLIB_DLL_SUFF },
145//#endif
146
147//#ifdef VBOX_WITH_SHARED_CLIPBOARD
148 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSharedClipboard" SUPLIB_DLL_SUFF },
149//#endif
150//#ifdef VBOX_WITH_SHARED_FOLDERS
151 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSharedFolders" SUPLIB_DLL_SUFF },
152//#endif
153//#ifdef VBOX_WITH_DRAG_AND_DROP
154 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxDragAndDropSvc" SUPLIB_DLL_SUFF },
155//#endif
156//#ifdef VBOX_WITH_GUEST_PROPS
157 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxGuestPropSvc" SUPLIB_DLL_SUFF },
158//#endif
159//#ifdef VBOX_WITH_GUEST_CONTROL
160 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxGuestControlSvc" SUPLIB_DLL_SUFF },
161//#endif
162 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxHostChannel" SUPLIB_DLL_SUFF },
163 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSharedCrOpenGL" SUPLIB_DLL_SUFF },
164 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxOGLhostcrutil" SUPLIB_DLL_SUFF },
165 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxOGLhosterrorspu" SUPLIB_DLL_SUFF },
166 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxOGLrenderspu" SUPLIB_DLL_SUFF },
167
168 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxManage" SUPLIB_EXE_SUFF },
169
170#ifdef VBOX_WITH_MAIN
171 { kSupIFT_Exe, kSupID_AppBin, false, "VBoxSVC" SUPLIB_EXE_SUFF },
172 #ifdef RT_OS_WINDOWS
173 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxC" SUPLIB_DLL_SUFF },
174 #else
175 { kSupIFT_Exe, kSupID_AppPrivArch, false, "VBoxXPCOMIPCD" SUPLIB_EXE_SUFF },
176 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxXPCOM" SUPLIB_DLL_SUFF },
177 { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxXPCOMIPCC" SUPLIB_DLL_SUFF },
178 { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxC" SUPLIB_DLL_SUFF },
179 { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxSVCM" SUPLIB_DLL_SUFF },
180 { kSupIFT_Data, kSupID_AppPrivArchComp, false, "VBoxXPCOMBase.xpt" },
181 #endif
182#endif
183
184 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VRDPAuth" SUPLIB_DLL_SUFF },
185 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxAuth" SUPLIB_DLL_SUFF },
186 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxVRDP" SUPLIB_DLL_SUFF },
187
188//#ifdef VBOX_WITH_HEADLESS
189 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxHeadless" SUPLIB_EXE_SUFF },
190 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxHeadless" SUPLIB_DLL_SUFF },
191 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxVideoRecFB" SUPLIB_DLL_SUFF },
192//#endif
193
194//#ifdef VBOX_WITH_QTGUI
195 { kSupIFT_Exe, kSupID_AppBin, true, "VirtualBox" SUPLIB_EXE_SUFF },
196 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VirtualBox" SUPLIB_DLL_SUFF },
197# ifdef RT_OS_DARWIN
198 { kSupIFT_Exe, kSupID_AppBin, true, "VirtualBoxVM" SUPLIB_EXE_SUFF },
199# endif
200# if !defined(RT_OS_DARWIN) && !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
201 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxKeyboard" SUPLIB_DLL_SUFF },
202# endif
203//#endif
204
205//#ifdef VBOX_WITH_VBOXSDL
206 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxSDL" SUPLIB_EXE_SUFF },
207 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSDL" SUPLIB_DLL_SUFF },
208//#endif
209
210//#ifdef VBOX_WITH_WEBSERVICES
211 { kSupIFT_Exe, kSupID_AppBin, true, "vboxwebsrv" SUPLIB_EXE_SUFF },
212//#endif
213
214#ifdef RT_OS_LINUX
215 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxTunctl" SUPLIB_EXE_SUFF },
216#endif
217
218//#ifdef VBOX_WITH_NETFLT
219 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxNetDHCP" SUPLIB_EXE_SUFF },
220 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxNetDHCP" SUPLIB_DLL_SUFF },
221//#endif
222
223//#ifdef VBOX_WITH_LWIP_NAT
224 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxNetNAT" SUPLIB_EXE_SUFF },
225 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxNetNAT" SUPLIB_DLL_SUFF },
226//#endif
227#if defined(VBOX_WITH_HARDENING) && defined(RT_OS_WINDOWS)
228# define HARDENED_TESTCASE_BIN_ENTRY(a_szName) \
229 { kSupIFT_TestExe, kSupID_AppBin, true, a_szName SUPLIB_EXE_SUFF }, \
230 { kSupIFT_TestDll, kSupID_AppBin, true, a_szName SUPLIB_DLL_SUFF }
231 HARDENED_TESTCASE_BIN_ENTRY("tstMicro"),
232 HARDENED_TESTCASE_BIN_ENTRY("tstPDMAsyncCompletion"),
233 HARDENED_TESTCASE_BIN_ENTRY("tstPDMAsyncCompletionStress"),
234 HARDENED_TESTCASE_BIN_ENTRY("tstVMM"),
235 HARDENED_TESTCASE_BIN_ENTRY("tstVMREQ"),
236# define HARDENED_TESTCASE_ENTRY(a_szName) \
237 { kSupIFT_TestExe, kSupID_Testcase, true, a_szName SUPLIB_EXE_SUFF }, \
238 { kSupIFT_TestDll, kSupID_Testcase, true, a_szName SUPLIB_DLL_SUFF }
239 HARDENED_TESTCASE_ENTRY("tstCFGM"),
240 HARDENED_TESTCASE_ENTRY("tstGIP-2"),
241 HARDENED_TESTCASE_ENTRY("tstIntNet-1"),
242 HARDENED_TESTCASE_ENTRY("tstMMHyperHeap"),
243 HARDENED_TESTCASE_ENTRY("tstRTR0ThreadPreemptionDriver"),
244 HARDENED_TESTCASE_ENTRY("tstRTR0MemUserKernelDriver"),
245 HARDENED_TESTCASE_ENTRY("tstRTR0SemMutexDriver"),
246 HARDENED_TESTCASE_ENTRY("tstRTR0TimerDriver"),
247 HARDENED_TESTCASE_ENTRY("tstSSM"),
248#endif
249};
250
251
252/** Array parallel to g_aSupInstallFiles containing per-file status info. */
253static SUPVERIFIEDFILE g_aSupVerifiedFiles[RT_ELEMENTS(g_aSupInstallFiles)];
254
255/** Array index by install directory specifier containing info about verified directories. */
256static SUPVERIFIEDDIR g_aSupVerifiedDirs[kSupID_End];
257
258
259/**
260 * Assembles the path to a directory.
261 *
262 * @returns VINF_SUCCESS on success, some error code on failure (fFatal
263 * decides whether it returns or not).
264 *
265 * @param enmDir The directory.
266 * @param pszDst Where to assemble the path.
267 * @param cchDst The size of the buffer.
268 * @param fFatal Whether failures should be treated as fatal (true) or not (false).
269 */
270static int supR3HardenedMakePath(SUPINSTDIR enmDir, char *pszDst, size_t cchDst, bool fFatal)
271{
272 int rc;
273 switch (enmDir)
274 {
275 case kSupID_AppBin:
276 rc = supR3HardenedPathAppBin(pszDst, cchDst);
277 break;
278 case kSupID_AppSharedLib:
279 rc = supR3HardenedPathAppSharedLibs(pszDst, cchDst);
280 break;
281 case kSupID_AppPrivArch:
282 rc = supR3HardenedPathAppPrivateArch(pszDst, cchDst);
283 break;
284 case kSupID_AppPrivArchComp:
285 rc = supR3HardenedPathAppPrivateArch(pszDst, cchDst);
286 if (RT_SUCCESS(rc))
287 {
288 size_t off = suplibHardenedStrLen(pszDst);
289 if (cchDst - off >= sizeof("/components"))
290 suplibHardenedMemCopy(&pszDst[off], "/components", sizeof("/components"));
291 else
292 rc = VERR_BUFFER_OVERFLOW;
293 }
294 break;
295 case kSupID_AppPrivNoArch:
296 rc = supR3HardenedPathAppPrivateNoArch(pszDst, cchDst);
297 break;
298 case kSupID_Testcase:
299 rc = supR3HardenedPathAppBin(pszDst, cchDst);
300 if (RT_SUCCESS(rc))
301 {
302 size_t off = suplibHardenedStrLen(pszDst);
303 if (cchDst - off >= sizeof("/testcase"))
304 suplibHardenedMemCopy(&pszDst[off], "/testcase", sizeof("/testcase"));
305 else
306 rc = VERR_BUFFER_OVERFLOW;
307 }
308 break;
309 default:
310 return supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
311 "supR3HardenedMakePath: enmDir=%d\n", enmDir);
312 }
313 if (RT_FAILURE(rc))
314 supR3HardenedError(rc, fFatal,
315 "supR3HardenedMakePath: enmDir=%d rc=%d\n", enmDir, rc);
316 return rc;
317}
318
319
320
321/**
322 * Assembles the path to a file table entry, with or without the actual filename.
323 *
324 * @returns VINF_SUCCESS on success, some error code on failure (fFatal
325 * decides whether it returns or not).
326 *
327 * @param pFile The file table entry.
328 * @param pszDst Where to assemble the path.
329 * @param cchDst The size of the buffer.
330 * @param fWithFilename If set, the filename is included, otherwise it is omitted (no trailing slash).
331 * @param fFatal Whether failures should be treated as fatal (true) or not (false).
332 */
333static int supR3HardenedMakeFilePath(PCSUPINSTFILE pFile, char *pszDst, size_t cchDst, bool fWithFilename, bool fFatal)
334{
335 /*
336 * Combine supR3HardenedMakePath and the filename.
337 */
338 int rc = supR3HardenedMakePath(pFile->enmDir, pszDst, cchDst, fFatal);
339 if (RT_SUCCESS(rc) && fWithFilename)
340 {
341 size_t cchFile = suplibHardenedStrLen(pFile->pszFile);
342 size_t off = suplibHardenedStrLen(pszDst);
343 if (cchDst - off >= cchFile + 2)
344 {
345 pszDst[off++] = '/';
346 suplibHardenedMemCopy(&pszDst[off], pFile->pszFile, cchFile + 1);
347 }
348 else
349 rc = supR3HardenedError(VERR_BUFFER_OVERFLOW, fFatal,
350 "supR3HardenedMakeFilePath: pszFile=%s off=%lu\n",
351 pFile->pszFile, (long)off);
352 }
353 return rc;
354}
355
356
357/**
358 * Verifies a directory.
359 *
360 * @returns VINF_SUCCESS on success. On failure, an error code is returned if
361 * fFatal is clear and if it's set the function wont return.
362 * @param enmDir The directory specifier.
363 * @param fFatal Whether validation failures should be treated as
364 * fatal (true) or not (false).
365 */
366DECLHIDDEN(int) supR3HardenedVerifyFixedDir(SUPINSTDIR enmDir, bool fFatal)
367{
368 /*
369 * Validate the index just to be on the safe side...
370 */
371 if (enmDir <= kSupID_Invalid || enmDir >= kSupID_End)
372 return supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
373 "supR3HardenedVerifyDir: enmDir=%d\n", enmDir);
374
375 /*
376 * Already validated?
377 */
378 if (g_aSupVerifiedDirs[enmDir].fValidated)
379 return VINF_SUCCESS; /** @todo revalidate? */
380
381 /* initialize the entry. */
382 if (g_aSupVerifiedDirs[enmDir].hDir != 0)
383 supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
384 "supR3HardenedVerifyDir: hDir=%p enmDir=%d\n",
385 (void *)g_aSupVerifiedDirs[enmDir].hDir, enmDir);
386 g_aSupVerifiedDirs[enmDir].hDir = -1;
387 g_aSupVerifiedDirs[enmDir].fValidated = false;
388
389 /*
390 * Make the path and open the directory.
391 */
392 char szPath[RTPATH_MAX];
393 int rc = supR3HardenedMakePath(enmDir, szPath, sizeof(szPath), fFatal);
394 if (RT_SUCCESS(rc))
395 {
396#if defined(RT_OS_WINDOWS)
397 PRTUTF16 pwszPath;
398 rc = RTStrToUtf16(szPath, &pwszPath);
399 if (RT_SUCCESS(rc))
400 {
401 HANDLE hDir = CreateFileW(pwszPath,
402 GENERIC_READ,
403 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
404 NULL,
405 OPEN_EXISTING,
406 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
407 NULL);
408 if (hDir != INVALID_HANDLE_VALUE)
409 {
410 /** @todo check the type */
411 /* That's all on windows, for now at least... */
412 g_aSupVerifiedDirs[enmDir].hDir = (intptr_t)hDir;
413 g_aSupVerifiedDirs[enmDir].fValidated = true;
414 }
415 else if (enmDir == kSupID_Testcase)
416 {
417 g_aSupVerifiedDirs[enmDir].fValidated = true;
418 rc = VINF_SUCCESS; /* Optional directory, ignore if missing. */
419 }
420 else
421 {
422 int err = RtlGetLastWin32Error();
423 rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal,
424 "supR3HardenedVerifyDir: Failed to open \"%s\": err=%d\n",
425 szPath, err);
426 }
427 RTUtf16Free(pwszPath);
428 }
429 else
430 rc = supR3HardenedError(rc, fFatal,
431 "supR3HardenedVerifyDir: Failed to convert \"%s\" to UTF-16: err=%d\n", szPath, rc);
432
433#else /* UNIXY */
434 int fd = open(szPath, O_RDONLY, 0);
435 if (fd >= 0)
436 {
437 /*
438 * On unixy systems we'll make sure the directory is owned by root
439 * and not writable by the group and user.
440 */
441 struct stat st;
442 if (!fstat(fd, &st))
443 {
444
445 if ( st.st_uid == 0
446 && !(st.st_mode & (S_IWGRP | S_IWOTH))
447 && S_ISDIR(st.st_mode))
448 {
449 g_aSupVerifiedDirs[enmDir].hDir = fd;
450 g_aSupVerifiedDirs[enmDir].fValidated = true;
451 }
452 else
453 {
454 if (!S_ISDIR(st.st_mode))
455 rc = supR3HardenedError(VERR_NOT_A_DIRECTORY, fFatal,
456 "supR3HardenedVerifyDir: \"%s\" is not a directory\n",
457 szPath, (long)st.st_uid);
458 else if (st.st_uid)
459 rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal,
460 "supR3HardenedVerifyDir: Cannot trust the directory \"%s\": not owned by root (st_uid=%ld)\n",
461 szPath, (long)st.st_uid);
462 else
463 rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal,
464 "supR3HardenedVerifyDir: Cannot trust the directory \"%s\": group and/or other writable (st_mode=0%lo)\n",
465 szPath, (long)st.st_mode);
466 close(fd);
467 }
468 }
469 else
470 {
471 int err = errno;
472 rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal,
473 "supR3HardenedVerifyDir: Failed to fstat \"%s\": %s (%d)\n",
474 szPath, strerror(err), err);
475 close(fd);
476 }
477 }
478 else if (enmDir == kSupID_Testcase)
479 {
480 g_aSupVerifiedDirs[enmDir].fValidated = true;
481 rc = VINF_SUCCESS; /* Optional directory, ignore if missing. */
482 }
483 else
484 {
485 int err = errno;
486 rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal,
487 "supR3HardenedVerifyDir: Failed to open \"%s\": %s (%d)\n",
488 szPath, strerror(err), err);
489 }
490#endif /* UNIXY */
491 }
492
493 return rc;
494}
495
496
497#ifdef RT_OS_WINDOWS
498/**
499 * Opens the file for verification.
500 *
501 * @returns VINF_SUCCESS on success. On failure, an error code is returned if
502 * fFatal is clear and if it's set the function wont return.
503 * @param pFile The file entry.
504 * @param fFatal Whether validation failures should be treated as
505 * kl fatal (true) or not (false).
506 * @param phFile The file handle, set to -1 if we failed to open
507 * the file. The function may return VINF_SUCCESS
508 * and a -1 handle if the file is optional.
509 */
510static int supR3HardenedVerifyFileOpen(PCSUPINSTFILE pFile, bool fFatal, intptr_t *phFile)
511{
512 *phFile = -1;
513
514 char szPath[RTPATH_MAX];
515 int rc = supR3HardenedMakeFilePath(pFile, szPath, sizeof(szPath), true /*fWithFilename*/, fFatal);
516 if (RT_SUCCESS(rc))
517 {
518 PRTUTF16 pwszPath;
519 rc = RTStrToUtf16(szPath, &pwszPath);
520 if (RT_SUCCESS(rc))
521 {
522 HANDLE hFile = CreateFileW(pwszPath,
523 GENERIC_READ,
524 FILE_SHARE_READ,
525 NULL,
526 OPEN_EXISTING,
527 FILE_ATTRIBUTE_NORMAL,
528 NULL);
529 if (hFile != INVALID_HANDLE_VALUE)
530 {
531 *phFile = (intptr_t)hFile;
532 rc = VINF_SUCCESS;
533 }
534 else
535 {
536 int err = RtlGetLastWin32Error();
537 if ( !pFile->fOptional
538 || ( err != ERROR_FILE_NOT_FOUND
539 && (err != ERROR_PATH_NOT_FOUND || pFile->enmDir != kSupID_Testcase) ) )
540 rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal,
541 "supR3HardenedVerifyFileInternal: Failed to open '%s': err=%d\n", szPath, err);
542 }
543 RTUtf16Free(pwszPath);
544 }
545 else
546 rc = supR3HardenedError(rc, fFatal, "supR3HardenedVerifyFileInternal: Failed to convert '%s' to UTF-16: %Rrc\n",
547 szPath, rc);
548 }
549 return rc;
550}
551
552
553/**
554 * Worker for supR3HardenedVerifyFileInternal.
555 *
556 * @returns VINF_SUCCESS on success. On failure, an error code is returned if
557 * fFatal is clear and if it's set the function wont return.
558 * @param pFile The file entry.
559 * @param pVerified The verification record.
560 * @param fFatal Whether validation failures should be treated as
561 * fatal (true) or not (false).
562 * @param fLeaveFileOpen Whether the file should be left open.
563 */
564static int supR3HardenedVerifyFileSignature(PCSUPINSTFILE pFile, PSUPVERIFIEDFILE pVerified, bool fFatal, bool fLeaveFileOpen)
565{
566# if defined(VBOX_WITH_HARDENING) && !defined(IN_SUP_R3_STATIC) /* Latter: Not in VBoxCpuReport and friends. */
567
568 /*
569 * Open the file if we have to.
570 */
571 int rc;
572 intptr_t hFileOpened;
573 intptr_t hFile = pVerified->hFile;
574 if (hFile != -1)
575 hFileOpened = -1;
576 else
577 {
578 rc = supR3HardenedVerifyFileOpen(pFile, fFatal, &hFileOpened);
579 if (RT_FAILURE(rc))
580 return rc;
581 hFile = hFileOpened;
582 }
583
584 /*
585 * Verify the signature.
586 */
587 char szErr[1024];
588 RTERRINFO ErrInfo;
589 RTErrInfoInit(&ErrInfo, szErr, sizeof(szErr));
590
591 uint32_t fFlags = SUPHNTVI_F_REQUIRE_BUILD_CERT;
592 if (pFile->enmType == kSupIFT_Rc)
593 fFlags |= SUPHNTVI_F_RC_IMAGE;
594
595 rc = supHardenedWinVerifyImageByHandleNoName((HANDLE)hFile, fFlags, &ErrInfo);
596 if (RT_SUCCESS(rc))
597 pVerified->fCheckedSignature = true;
598 else
599 {
600 pVerified->fCheckedSignature = false;
601 rc = supR3HardenedError(rc, fFatal, "supR3HardenedVerifyFileInternal: '%s': Image verify error rc=%Rrc: %s\n",
602 pFile->pszFile, rc, szErr);
603
604 }
605
606 /*
607 * Close the handle if we opened the file and we should close it.
608 */
609 if (hFileOpened != -1)
610 {
611 if (fLeaveFileOpen && RT_SUCCESS(rc))
612 pVerified->hFile = hFileOpened;
613 else
614 NtClose((HANDLE)hFileOpened);
615 }
616
617 return rc;
618
619# else /* Not checking signatures. */
620 return VINF_SUCCESS;
621# endif /* Not checking signatures. */
622}
623#endif
624
625
626/**
627 * Verifies a file entry.
628 *
629 * @returns VINF_SUCCESS on success. On failure, an error code is returned if
630 * fFatal is clear and if it's set the function wont return.
631 *
632 * @param iFile The file table index of the file to be verified.
633 * @param fFatal Whether validation failures should be treated as
634 * fatal (true) or not (false).
635 * @param fLeaveFileOpen Whether the file should be left open.
636 * @param fVerifyAll Set if this is an verify all call and we will
637 * postpone signature checking.
638 */
639static int supR3HardenedVerifyFileInternal(int iFile, bool fFatal, bool fLeaveFileOpen, bool fVerifyAll)
640{
641 PCSUPINSTFILE pFile = &g_aSupInstallFiles[iFile];
642 PSUPVERIFIEDFILE pVerified = &g_aSupVerifiedFiles[iFile];
643
644 /*
645 * Already done validation? Do signature validation if we haven't yet.
646 */
647 if (pVerified->fValidated)
648 {
649 /** @todo revalidate? Check that the file hasn't been replace or similar. */
650#ifdef RT_OS_WINDOWS
651 if (!pVerified->fCheckedSignature && !fVerifyAll)
652 return supR3HardenedVerifyFileSignature(pFile, pVerified, fFatal, fLeaveFileOpen);
653#endif
654 return VINF_SUCCESS;
655 }
656
657
658 /* initialize the entry. */
659 if (pVerified->hFile != 0)
660 supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
661 "supR3HardenedVerifyFileInternal: hFile=%p (%s)\n",
662 (void *)pVerified->hFile, pFile->pszFile);
663 pVerified->hFile = -1;
664 pVerified->fValidated = false;
665#ifdef RT_OS_WINDOWS
666 pVerified->fCheckedSignature = false;
667#endif
668
669 /*
670 * Verify the directory then proceed to open it.
671 * (This'll make sure the directory is opened and that we can (later)
672 * use openat if we wish.)
673 */
674 int rc = supR3HardenedVerifyFixedDir(pFile->enmDir, fFatal);
675 if (RT_SUCCESS(rc))
676 {
677#if defined(RT_OS_WINDOWS)
678 rc = supR3HardenedVerifyFileOpen(pFile, fFatal, &pVerified->hFile);
679 if (RT_SUCCESS(rc))
680 {
681 if (!fVerifyAll)
682 rc = supR3HardenedVerifyFileSignature(pFile, pVerified, fFatal, fLeaveFileOpen);
683 if (RT_SUCCESS(rc))
684 {
685 pVerified->fValidated = true;
686 if (!fLeaveFileOpen)
687 {
688 NtClose((HANDLE)pVerified->hFile);
689 pVerified->hFile = -1;
690 }
691 }
692 }
693#else /* !RT_OS_WINDOWS */
694 char szPath[RTPATH_MAX];
695 rc = supR3HardenedMakeFilePath(pFile, szPath, sizeof(szPath), true /*fWithFilename*/, fFatal);
696 if (RT_SUCCESS(rc))
697 {
698 int fd = open(szPath, O_RDONLY, 0);
699 if (fd >= 0)
700 {
701 /*
702 * On unixy systems we'll make sure the file is owned by root
703 * and not writable by the group and user.
704 */
705 struct stat st;
706 if (!fstat(fd, &st))
707 {
708 if ( st.st_uid == 0
709 && !(st.st_mode & (S_IWGRP | S_IWOTH))
710 && S_ISREG(st.st_mode))
711 {
712 /* it's valid. */
713 if (fLeaveFileOpen)
714 pVerified->hFile = fd;
715 else
716 close(fd);
717 pVerified->fValidated = true;
718 }
719 else
720 {
721 if (!S_ISREG(st.st_mode))
722 rc = supR3HardenedError(VERR_IS_A_DIRECTORY, fFatal,
723 "supR3HardenedVerifyFileInternal: \"%s\" is not a regular file\n",
724 szPath, (long)st.st_uid);
725 else if (st.st_uid)
726 rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal,
727 "supR3HardenedVerifyFileInternal: Cannot trust the file \"%s\": not owned by root (st_uid=%ld)\n",
728 szPath, (long)st.st_uid);
729 else
730 rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal,
731 "supR3HardenedVerifyFileInternal: Cannot trust the file \"%s\": group and/or other writable (st_mode=0%lo)\n",
732 szPath, (long)st.st_mode);
733 close(fd);
734 }
735 }
736 else
737 {
738 int err = errno;
739 rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal,
740 "supR3HardenedVerifyFileInternal: Failed to fstat \"%s\": %s (%d)\n",
741 szPath, strerror(err), err);
742 close(fd);
743 }
744 }
745 else
746 {
747 int err = errno;
748 if (!pFile->fOptional || err != ENOENT)
749 rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal,
750 "supR3HardenedVerifyFileInternal: Failed to open \"%s\": %s (%d)\n",
751 szPath, strerror(err), err);
752 }
753 }
754#endif /* !RT_OS_WINDOWS */
755 }
756
757 return rc;
758}
759
760
761/**
762 * Verifies that the specified table entry matches the given filename.
763 *
764 * @returns VINF_SUCCESS if matching. On mismatch fFatal indicates whether an
765 * error is returned or we terminate the application.
766 *
767 * @param iFile The file table index.
768 * @param pszFilename The filename.
769 * @param fFatal Whether validation failures should be treated as
770 * fatal (true) or not (false).
771 */
772static int supR3HardenedVerifySameFile(int iFile, const char *pszFilename, bool fFatal)
773{
774 PCSUPINSTFILE pFile = &g_aSupInstallFiles[iFile];
775
776 /*
777 * Construct the full path for the file table entry
778 * and compare it with the specified file.
779 */
780 char szName[RTPATH_MAX];
781 int rc = supR3HardenedMakeFilePath(pFile, szName, sizeof(szName), true /*fWithFilename*/, fFatal);
782 if (RT_FAILURE(rc))
783 return rc;
784 if (SUP_COMP_FILENAME(szName, pszFilename))
785 {
786 /*
787 * Normalize the two paths and compare again.
788 */
789 rc = VERR_NOT_SAME_DEVICE;
790#if defined(RT_OS_WINDOWS)
791 LPSTR pszIgnored;
792 char szName2[RTPATH_MAX]; /** @todo Must use UTF-16 here! Code is mixing UTF-8 and native. */
793 if ( GetFullPathName(szName, RT_ELEMENTS(szName2), &szName2[0], &pszIgnored)
794 && GetFullPathName(pszFilename, RT_ELEMENTS(szName), &szName[0], &pszIgnored))
795 if (!SUP_COMP_FILENAME(szName2, szName))
796 rc = VINF_SUCCESS;
797#else
798 AssertCompile(RTPATH_MAX >= PATH_MAX);
799 char szName2[RTPATH_MAX];
800 if ( realpath(szName, szName2) != NULL
801 && realpath(pszFilename, szName) != NULL)
802 if (!SUP_COMP_FILENAME(szName2, szName))
803 rc = VINF_SUCCESS;
804#endif
805
806 if (RT_FAILURE(rc))
807 {
808 supR3HardenedMakeFilePath(pFile, szName, sizeof(szName), true /*fWithFilename*/, fFatal);
809 return supR3HardenedError(rc, fFatal,
810 "supR3HardenedVerifySameFile: \"%s\" isn't the same as \"%s\"\n",
811 pszFilename, szName);
812 }
813 }
814
815 /*
816 * Check more stuff like the stat info if it's an already open file?
817 */
818
819
820
821 return VINF_SUCCESS;
822}
823
824
825/**
826 * Verifies a file.
827 *
828 * @returns VINF_SUCCESS on success.
829 * VERR_NOT_FOUND if the file isn't in the table, this isn't ever a fatal error.
830 * On verification failure, an error code will be returned when fFatal is clear,
831 * otherwise the program will be terminated.
832 *
833 * @param pszFilename The filename.
834 * @param fFatal Whether validation failures should be treated as
835 * fatal (true) or not (false).
836 */
837DECLHIDDEN(int) supR3HardenedVerifyFixedFile(const char *pszFilename, bool fFatal)
838{
839 /*
840 * Lookup the file and check if it's the same file.
841 */
842 const char *pszName = supR3HardenedPathFilename(pszFilename);
843 for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++)
844 if (!SUP_COMP_FILENAME(pszName, g_aSupInstallFiles[iFile].pszFile))
845 {
846 int rc = supR3HardenedVerifySameFile(iFile, pszFilename, fFatal);
847 if (RT_SUCCESS(rc))
848 rc = supR3HardenedVerifyFileInternal(iFile, fFatal, false /* fLeaveFileOpen */, false /* fVerifyAll */);
849 return rc;
850 }
851
852 return VERR_NOT_FOUND;
853}
854
855
856/**
857 * Verifies a program, worker for supR3HardenedVerifyAll.
858 *
859 * @returns See supR3HardenedVerifyAll.
860 * @param pszProgName See supR3HardenedVerifyAll.
861 * @param pszExePath The path to the executable.
862 * @param fFatal See supR3HardenedVerifyAll.
863 * @param fLeaveOpen The leave open setting used by
864 * supR3HardenedVerifyAll.
865 * @param fMainFlags Flags supplied to SUPR3HardenedMain.
866 */
867static int supR3HardenedVerifyProgram(const char *pszProgName, const char *pszExePath, bool fFatal,
868 bool fLeaveOpen, uint32_t fMainFlags)
869{
870 /*
871 * Search the table looking for the executable and the DLL/DYLIB/SO.
872 * Note! On darwin we have a hack in place for VirtualBoxVM helper app
873 * to share VirtualBox.dylib with the VirtualBox app. This ASSUMES
874 * that cchProgNameDll is equal or shorter to the exe name.
875 */
876 int rc = VINF_SUCCESS;
877 bool fExe = false;
878 bool fDll = false;
879 size_t const cchProgNameExe = suplibHardenedStrLen(pszProgName);
880#ifndef RT_OS_DARWIN
881 size_t const cchProgNameDll = cchProgNameExe;
882#else
883 size_t const cchProgNameDll = fMainFlags & SUPSECMAIN_FLAGS_OSX_VM_APP
884 ? sizeof("VirtualBox") - 1
885 : cchProgNameExe;
886 if (cchProgNameDll > cchProgNameExe)
887 return supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
888 "supR3HardenedVerifyProgram: SUPSECMAIN_FLAGS_OSX_VM_APP + '%s'", pszProgName);
889#endif
890 for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++)
891 if (!suplibHardenedStrNCmp(pszProgName, g_aSupInstallFiles[iFile].pszFile, cchProgNameDll))
892 {
893 if ( ( g_aSupInstallFiles[iFile].enmType == kSupIFT_Dll
894 || g_aSupInstallFiles[iFile].enmType == kSupIFT_TestDll)
895 && !suplibHardenedStrCmp(&g_aSupInstallFiles[iFile].pszFile[cchProgNameDll], SUPLIB_DLL_SUFF))
896 {
897 /* This only has to be found (once). */
898 if (fDll)
899 rc = supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
900 "supR3HardenedVerifyProgram: duplicate DLL entry for \"%s\"\n", pszProgName);
901 else
902 rc = supR3HardenedVerifyFileInternal(iFile, fFatal, fLeaveOpen,
903 true /* fVerifyAll - check sign later, only final process need check it on load. */);
904 fDll = true;
905 }
906 else if ( ( g_aSupInstallFiles[iFile].enmType == kSupIFT_Exe
907 || g_aSupInstallFiles[iFile].enmType == kSupIFT_TestExe)
908 && ( cchProgNameExe == cchProgNameDll
909 || !suplibHardenedStrNCmp(pszProgName, g_aSupInstallFiles[iFile].pszFile, cchProgNameExe))
910 && !suplibHardenedStrCmp(&g_aSupInstallFiles[iFile].pszFile[cchProgNameExe], SUPLIB_EXE_SUFF))
911 {
912 /* Here we'll have to check that the specific program is the same as the entry. */
913 if (fExe)
914 rc = supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
915 "supR3HardenedVerifyProgram: duplicate EXE entry for \"%s\"\n", pszProgName);
916 else
917 rc = supR3HardenedVerifyFileInternal(iFile, fFatal, fLeaveOpen, false /* fVerifyAll */);
918 fExe = true;
919
920 supR3HardenedVerifySameFile(iFile, pszExePath, fFatal);
921 }
922 }
923
924 /*
925 * Check the findings.
926 */
927 if (RT_SUCCESS(rc))
928 {
929 if (!fDll && !fExe)
930 rc = supR3HardenedError(VERR_NOT_FOUND, fFatal,
931 "supR3HardenedVerifyProgram: Couldn't find the program \"%s\"\n", pszProgName);
932 else if (!fExe)
933 rc = supR3HardenedError(VERR_NOT_FOUND, fFatal,
934 "supR3HardenedVerifyProgram: Couldn't find the EXE entry for \"%s\"\n", pszProgName);
935 else if (!fDll)
936 rc = supR3HardenedError(VERR_NOT_FOUND, fFatal,
937 "supR3HardenedVerifyProgram: Couldn't find the DLL entry for \"%s\"\n", pszProgName);
938 }
939 return rc;
940}
941
942
943/**
944 * Verifies all the known files (called from SUPR3HardenedMain).
945 *
946 * @returns VINF_SUCCESS on success.
947 * On verification failure, an error code will be returned when fFatal is clear,
948 * otherwise the program will be terminated.
949 *
950 * @param fFatal Whether validation failures should be treated as
951 * fatal (true) or not (false).
952 * @param pszProgName The program name. This is used to verify that
953 * both the executable and corresponding
954 * DLL/DYLIB/SO are valid.
955 * @param pszExePath The path to the executable.
956 * @param fMainFlags Flags supplied to SUPR3HardenedMain.
957 */
958DECLHIDDEN(int) supR3HardenedVerifyAll(bool fFatal, const char *pszProgName, const char *pszExePath, uint32_t fMainFlags)
959{
960 /*
961 * On windows
962 */
963#if defined(RT_OS_WINDOWS)
964 bool fLeaveOpen = true;
965#else
966 bool fLeaveOpen = false;
967#endif
968
969 /*
970 * The verify all the files.
971 */
972 int rc = VINF_SUCCESS;
973 for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++)
974 {
975 int rc2 = supR3HardenedVerifyFileInternal(iFile, fFatal, fLeaveOpen, true /* fVerifyAll */);
976 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
977 rc = rc2;
978 }
979
980 /*
981 * Verify the program name, that is to say, check that it's in the table
982 * (thus verified above) and verify the signature on platforms where we
983 * sign things.
984 */
985 int rc2 = supR3HardenedVerifyProgram(pszProgName, pszExePath, fFatal, fLeaveOpen, fMainFlags);
986 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
987 rc2 = rc;
988
989 return rc;
990}
991
992
993/**
994 * Copies the N messages into the error buffer and returns @a rc.
995 *
996 * @returns Returns @a rc
997 * @param rc The return code.
998 * @param pErrInfo The error info structure.
999 * @param cMsgs The number of messages in the ellipsis.
1000 * @param ... Message parts.
1001 */
1002static int supR3HardenedSetErrorN(int rc, PRTERRINFO pErrInfo, unsigned cMsgs, ...)
1003{
1004 if (pErrInfo)
1005 {
1006 size_t cbErr = pErrInfo->cbMsg;
1007 char *pszErr = pErrInfo->pszMsg;
1008
1009 va_list va;
1010 va_start(va, cMsgs);
1011 while (cMsgs-- > 0 && cbErr > 0)
1012 {
1013 const char *pszMsg = va_arg(va, const char *);
1014 size_t cchMsg = VALID_PTR(pszMsg) ? suplibHardenedStrLen(pszMsg) : 0;
1015 if (cchMsg >= cbErr)
1016 cchMsg = cbErr - 1;
1017 suplibHardenedMemCopy(pszErr, pszMsg, cchMsg);
1018 pszErr[cchMsg] = '\0';
1019 pszErr += cchMsg;
1020 cbErr -= cchMsg;
1021 }
1022 va_end(va);
1023
1024 pErrInfo->rc = rc;
1025 pErrInfo->fFlags |= RTERRINFO_FLAGS_SET;
1026 }
1027
1028 return rc;
1029}
1030
1031
1032/**
1033 * Copies the three messages into the error buffer and returns @a rc.
1034 *
1035 * @returns Returns @a rc
1036 * @param rc The return code.
1037 * @param pErrInfo The error info structure.
1038 * @param pszMsg1 The first message part.
1039 * @param pszMsg2 The second message part.
1040 * @param pszMsg3 The third message part.
1041 */
1042static int supR3HardenedSetError3(int rc, PRTERRINFO pErrInfo, const char *pszMsg1,
1043 const char *pszMsg2, const char *pszMsg3)
1044{
1045 return supR3HardenedSetErrorN(rc, pErrInfo, 3, pszMsg1, pszMsg2, pszMsg3);
1046}
1047
1048#ifdef SOME_UNUSED_FUNCTION
1049
1050/**
1051 * Copies the two messages into the error buffer and returns @a rc.
1052 *
1053 * @returns Returns @a rc
1054 * @param rc The return code.
1055 * @param pErrInfo The error info structure.
1056 * @param pszMsg1 The first message part.
1057 * @param pszMsg2 The second message part.
1058 */
1059static int supR3HardenedSetError2(int rc, PRTERRINFO pErrInfo, const char *pszMsg1,
1060 const char *pszMsg2)
1061{
1062 return supR3HardenedSetErrorN(rc, pErrInfo, 2, pszMsg1, pszMsg2);
1063}
1064
1065
1066/**
1067 * Copies the error message to the error buffer and returns @a rc.
1068 *
1069 * @returns Returns @a rc
1070 * @param rc The return code.
1071 * @param pErrInfo The error info structure.
1072 * @param pszMsg The message.
1073 */
1074static int supR3HardenedSetError(int rc, PRTERRINFO pErrInfo, const char *pszMsg)
1075{
1076 return supR3HardenedSetErrorN(rc, pErrInfo, 1, pszMsg);
1077}
1078
1079#endif /* SOME_UNUSED_FUNCTION */
1080
1081/**
1082 * Output from a successfull supR3HardenedVerifyPathSanity call.
1083 */
1084typedef struct SUPR3HARDENEDPATHINFO
1085{
1086 /** The length of the path in szCopy. */
1087 uint16_t cch;
1088 /** The number of path components. */
1089 uint16_t cComponents;
1090 /** Set if the path ends with slash, indicating that it's a directory
1091 * reference and not a file reference. The slash has been removed from
1092 * the copy. */
1093 bool fDirSlash;
1094 /** The offset where each path component starts, i.e. the char after the
1095 * slash. The array has cComponents + 1 entries, where the final one is
1096 * cch + 1 so that one can always terminate the current component by
1097 * szPath[aoffComponent[i] - 1] = '\0'. */
1098 uint16_t aoffComponents[32+1];
1099 /** A normalized copy of the path.
1100 * Reserve some extra space so we can be more relaxed about overflow
1101 * checks and terminator paddings, especially when recursing. */
1102 char szPath[SUPR3HARDENED_MAX_PATH * 2];
1103} SUPR3HARDENEDPATHINFO;
1104/** Pointer to a parsed path. */
1105typedef SUPR3HARDENEDPATHINFO *PSUPR3HARDENEDPATHINFO;
1106
1107
1108/**
1109 * Verifies that the path is absolutely sane, it also parses the path.
1110 *
1111 * A sane path starts at the root (w/ drive letter on DOS derived systems) and
1112 * does not have any relative bits (/../) or unnecessary slashes (/bin//ls).
1113 * Sane paths are less or equal to SUPR3HARDENED_MAX_PATH bytes in length. UNC
1114 * paths are not supported.
1115 *
1116 * @returns VBox status code.
1117 * @param pszPath The path to check.
1118 * @param pErrInfo The error info structure.
1119 * @param pInfo Where to return a copy of the path along with
1120 * parsing information.
1121 */
1122static int supR3HardenedVerifyPathSanity(const char *pszPath, PRTERRINFO pErrInfo, PSUPR3HARDENEDPATHINFO pInfo)
1123{
1124 const char *pszSrc = pszPath;
1125 char *pszDst = pInfo->szPath;
1126
1127 /*
1128 * Check that it's an absolute path and copy the volume/root specifier.
1129 */
1130#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1131 if ( !RT_C_IS_ALPHA(pszSrc[0])
1132 || pszSrc[1] != ':'
1133 || !RTPATH_IS_SLASH(pszSrc[2]))
1134 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo, "The path is not absolute: '", pszPath, "'");
1135
1136 *pszDst++ = RT_C_TO_UPPER(pszSrc[0]);
1137 *pszDst++ = ':';
1138 *pszDst++ = RTPATH_SLASH;
1139 pszSrc += 3;
1140
1141#else
1142 if (!RTPATH_IS_SLASH(pszSrc[0]))
1143 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo, "The path is not absolute: '", pszPath, "'");
1144
1145 *pszDst++ = RTPATH_SLASH;
1146 pszSrc += 1;
1147#endif
1148
1149 /*
1150 * No path specifying the root or something very shortly thereafter will
1151 * be approved of.
1152 */
1153 if (pszSrc[0] == '\0')
1154 return supR3HardenedSetError3(VERR_SUPLIB_PATH_IS_ROOT, pErrInfo, "The path is root: '", pszPath, "'");
1155 if ( pszSrc[1] == '\0'
1156 || pszSrc[2] == '\0')
1157 return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_SHORT, pErrInfo, "The path is too short: '", pszPath, "'");
1158
1159 /*
1160 * Check each component. No parent references or double slashes.
1161 */
1162 pInfo->cComponents = 0;
1163 pInfo->fDirSlash = false;
1164 while (pszSrc[0])
1165 {
1166 /* Sanity checks. */
1167 if (RTPATH_IS_SLASH(pszSrc[0])) /* can be relaxed if we care. */
1168 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_CLEAN, pErrInfo,
1169 "The path is not clean of double slashes: '", pszPath, "'");
1170 if ( pszSrc[0] == '.'
1171 && pszSrc[1] == '.'
1172 && RTPATH_IS_SLASH(pszSrc[2]))
1173 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo,
1174 "The path is not absolute: '", pszPath, "'");
1175
1176 /* Record the start of the component. */
1177 if (pInfo->cComponents >= RT_ELEMENTS(pInfo->aoffComponents) - 1)
1178 return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_MANY_COMPONENTS, pErrInfo,
1179 "The path has too many components: '", pszPath, "'");
1180 pInfo->aoffComponents[pInfo->cComponents++] = pszDst - &pInfo->szPath[0];
1181
1182 /* Traverse to the end of the component, copying it as we go along. */
1183 while (pszSrc[0])
1184 {
1185 if (RTPATH_IS_SLASH(pszSrc[0]))
1186 {
1187 pszSrc++;
1188 if (*pszSrc)
1189 *pszDst++ = RTPATH_SLASH;
1190 else
1191 pInfo->fDirSlash = true;
1192 break;
1193 }
1194 *pszDst++ = *pszSrc++;
1195 if ((uintptr_t)(pszDst - &pInfo->szPath[0]) >= SUPR3HARDENED_MAX_PATH)
1196 return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo,
1197 "The path is too long: '", pszPath, "'");
1198 }
1199 }
1200
1201 /* Terminate the string and enter its length. */
1202 pszDst[0] = '\0';
1203 pszDst[1] = '\0'; /* for aoffComponents */
1204 pInfo->cch = (uint16_t)(pszDst - &pInfo->szPath[0]);
1205 pInfo->aoffComponents[pInfo->cComponents] = pInfo->cch + 1;
1206
1207 return VINF_SUCCESS;
1208}
1209
1210
1211/**
1212 * The state information collected by supR3HardenedVerifyFsObject.
1213 *
1214 * This can be used to verify that a directory we've opened for enumeration is
1215 * the same as the one that supR3HardenedVerifyFsObject just verified. It can
1216 * equally be used to verify a native specfied by the user.
1217 */
1218typedef struct SUPR3HARDENEDFSOBJSTATE
1219{
1220#ifdef RT_OS_WINDOWS
1221 /** Not implemented for windows yet. */
1222 char chTodo;
1223#else
1224 /** The stat output. */
1225 struct stat Stat;
1226#endif
1227} SUPR3HARDENEDFSOBJSTATE;
1228/** Pointer to a file system object state. */
1229typedef SUPR3HARDENEDFSOBJSTATE *PSUPR3HARDENEDFSOBJSTATE;
1230/** Pointer to a const file system object state. */
1231typedef SUPR3HARDENEDFSOBJSTATE const *PCSUPR3HARDENEDFSOBJSTATE;
1232
1233
1234/**
1235 * Query information about a file system object by path.
1236 *
1237 * @returns VBox status code, error buffer filled on failure.
1238 * @param pszPath The path to the object.
1239 * @param pFsObjState Where to return the state information.
1240 * @param pErrInfo The error info structure.
1241 */
1242static int supR3HardenedQueryFsObjectByPath(char const *pszPath, PSUPR3HARDENEDFSOBJSTATE pFsObjState, PRTERRINFO pErrInfo)
1243{
1244#if defined(RT_OS_WINDOWS)
1245 /** @todo Windows hardening. */
1246 pFsObjState->chTodo = 0;
1247 return VINF_SUCCESS;
1248
1249#else
1250 /*
1251 * Stat the object, do not follow links.
1252 */
1253 if (lstat(pszPath, &pFsObjState->Stat) != 0)
1254 {
1255 /* Ignore access errors */
1256 if (errno != EACCES)
1257 return supR3HardenedSetErrorN(VERR_SUPLIB_STAT_FAILED, pErrInfo,
1258 5, "stat failed with ", strerror(errno), " on: '", pszPath, "'");
1259 }
1260
1261 /*
1262 * Read ACLs.
1263 */
1264 /** @todo */
1265
1266 return VINF_SUCCESS;
1267#endif
1268}
1269
1270
1271/**
1272 * Query information about a file system object by native handle.
1273 *
1274 * @returns VBox status code, error buffer filled on failure.
1275 * @param hNative The native handle to the object @a pszPath
1276 * specifies and this should be verified to be the
1277 * same file system object.
1278 * @param pFsObjState Where to return the state information.
1279 * @param pszPath The path to the object. (For the error message
1280 * only.)
1281 * @param pErrInfo The error info structure.
1282 */
1283static int supR3HardenedQueryFsObjectByHandle(RTHCUINTPTR hNative, PSUPR3HARDENEDFSOBJSTATE pFsObjState,
1284 char const *pszPath, PRTERRINFO pErrInfo)
1285{
1286#if defined(RT_OS_WINDOWS)
1287 /** @todo Windows hardening. */
1288 pFsObjState->chTodo = 0;
1289 return VINF_SUCCESS;
1290
1291#else
1292 /*
1293 * Stat the object, do not follow links.
1294 */
1295 if (fstat((int)hNative, &pFsObjState->Stat) != 0)
1296 return supR3HardenedSetErrorN(VERR_SUPLIB_STAT_FAILED, pErrInfo,
1297 5, "fstat failed with ", strerror(errno), " on '", pszPath, "'");
1298
1299 /*
1300 * Read ACLs.
1301 */
1302 /** @todo */
1303
1304 return VINF_SUCCESS;
1305#endif
1306}
1307
1308
1309/**
1310 * Verifies that the file system object indicated by the native handle is the
1311 * same as the one @a pFsObjState indicates.
1312 *
1313 * @returns VBox status code, error buffer filled on failure.
1314 * @param pFsObjState1 File system object information/state by path.
1315 * @param pFsObjState2 File system object information/state by handle.
1316 * @param pszPath The path to the object @a pFsObjState
1317 * describes. (For the error message.)
1318 * @param pErrInfo The error info structure.
1319 */
1320static int supR3HardenedIsSameFsObject(PCSUPR3HARDENEDFSOBJSTATE pFsObjState1, PCSUPR3HARDENEDFSOBJSTATE pFsObjState2,
1321 const char *pszPath, PRTERRINFO pErrInfo)
1322{
1323#if defined(RT_OS_WINDOWS)
1324 /** @todo Windows hardening. */
1325 return VINF_SUCCESS;
1326
1327#elif defined(RT_OS_OS2)
1328 return VINF_SUCCESS;
1329
1330#else
1331 /*
1332 * Compare the ino+dev, then the uid+gid and finally the important mode
1333 * bits. Technically the first one should be enough, but we're paranoid.
1334 */
1335 if ( pFsObjState1->Stat.st_ino != pFsObjState2->Stat.st_ino
1336 || pFsObjState1->Stat.st_dev != pFsObjState2->Stat.st_dev)
1337 return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo,
1338 "The native handle is not the same as '", pszPath, "' (ino/dev)");
1339 if ( pFsObjState1->Stat.st_uid != pFsObjState2->Stat.st_uid
1340 || pFsObjState1->Stat.st_gid != pFsObjState2->Stat.st_gid)
1341 return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo,
1342 "The native handle is not the same as '", pszPath, "' (uid/gid)");
1343 if ( (pFsObjState1->Stat.st_mode & (S_IFMT | S_IWUSR | S_IWGRP | S_IWOTH))
1344 != (pFsObjState2->Stat.st_mode & (S_IFMT | S_IWUSR | S_IWGRP | S_IWOTH)))
1345 return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo,
1346 "The native handle is not the same as '", pszPath, "' (mode)");
1347 return VINF_SUCCESS;
1348#endif
1349}
1350
1351
1352/**
1353 * Verifies a file system object (file or directory).
1354 *
1355 * @returns VBox status code, error buffer filled on failure.
1356 * @param pFsObjState The file system object information/state to be
1357 * verified.
1358 * @param fDir Whether this is a directory or a file.
1359 * @param fRelaxed Whether we can be more relaxed about this
1360 * directory (only used for grand parent
1361 * directories).
1362 * @param pszPath The path to the object. For error messages and
1363 * securing a couple of hacks.
1364 * @param pErrInfo The error info structure.
1365 */
1366static int supR3HardenedVerifyFsObject(PCSUPR3HARDENEDFSOBJSTATE pFsObjState, bool fDir, bool fRelaxed,
1367 const char *pszPath, PRTERRINFO pErrInfo)
1368{
1369#if defined(RT_OS_WINDOWS)
1370 /** @todo Windows hardening. */
1371 NOREF(pFsObjState); NOREF(fDir); NOREF(fRelaxed); NOREF(pszPath); NOREF(pErrInfo);
1372 return VINF_SUCCESS;
1373
1374#elif defined(RT_OS_OS2)
1375 /* No hardening here - it's a single user system. */
1376 NOREF(pFsObjState); NOREF(fDir); NOREF(fRelaxed); NOREF(pszPath); NOREF(pErrInfo);
1377 return VINF_SUCCESS;
1378
1379#else
1380 /*
1381 * The owner must be root.
1382 *
1383 * This can be extended to include predefined system users if necessary.
1384 */
1385 if (pFsObjState->Stat.st_uid != 0)
1386 return supR3HardenedSetError3(VERR_SUPLIB_OWNER_NOT_ROOT, pErrInfo, "The owner is not root: '", pszPath, "'");
1387
1388 /*
1389 * The object type must be directory or file, no symbolic links or other
1390 * risky stuff (sorry dude, but we're paranoid on purpose here).
1391 */
1392 if ( !S_ISDIR(pFsObjState->Stat.st_mode)
1393 && !S_ISREG(pFsObjState->Stat.st_mode))
1394 {
1395 if (S_ISLNK(pFsObjState->Stat.st_mode))
1396 return supR3HardenedSetError3(VERR_SUPLIB_SYMLINKS_ARE_NOT_PERMITTED, pErrInfo,
1397 "Symlinks are not permitted: '", pszPath, "'");
1398 return supR3HardenedSetError3(VERR_SUPLIB_NOT_DIR_NOT_FILE, pErrInfo,
1399 "Not regular file or directory: '", pszPath, "'");
1400 }
1401 if (fDir != !!S_ISDIR(pFsObjState->Stat.st_mode))
1402 {
1403 if (S_ISDIR(pFsObjState->Stat.st_mode))
1404 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
1405 "Expected file but found directory: '", pszPath, "'");
1406 return supR3HardenedSetError3(VERR_SUPLIB_IS_FILE, pErrInfo,
1407 "Expected directory but found file: '", pszPath, "'");
1408 }
1409
1410 /*
1411 * The group does not matter if it does not have write access, if it has
1412 * write access it must be group 0 (root/wheel/whatever).
1413 *
1414 * This can be extended to include predefined system groups or groups that
1415 * only root is member of.
1416 */
1417 if ( (pFsObjState->Stat.st_mode & S_IWGRP)
1418 && pFsObjState->Stat.st_gid != 0)
1419 {
1420#ifdef RT_OS_DARWIN
1421 /* HACK ALERT: On Darwin /Applications is root:admin with admin having
1422 full access. So, to work around we relax the hardening a bit and
1423 permit grand parents and beyond to be group writable by admin. */
1424 /** @todo dynamically resolve the admin group? */
1425 bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 80 /*admin*/ || suplibHardenedStrCmp(pszPath, "/Applications");
1426
1427#elif defined(RT_OS_FREEBSD)
1428 /* HACK ALERT: PC-BSD 9 has group-writable /usr/pib directory which is
1429 similar to /Applications on OS X (see above).
1430 On FreeBSD root is normally the only member of this group, on
1431 PC-BSD the default user is a member. */
1432 /** @todo dynamically resolve the operator group? */
1433 bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 5 /*operator*/ || suplibHardenedStrCmp(pszPath, "/usr/pbi");
1434 NOREF(fRelaxed);
1435#else
1436 NOREF(fRelaxed);
1437 bool fBad = true;
1438#endif
1439 if (fBad)
1440 return supR3HardenedSetError3(VERR_SUPLIB_WRITE_NON_SYS_GROUP, pErrInfo,
1441 "An unknown (and thus untrusted) group has write access to '", pszPath,
1442 "' and we therefore cannot trust the directory content or that of any subdirectory");
1443 }
1444
1445 /*
1446 * World must not have write access. There is no relaxing this rule.
1447 */
1448 if (pFsObjState->Stat.st_mode & S_IWOTH)
1449 return supR3HardenedSetError3(VERR_SUPLIB_WORLD_WRITABLE, pErrInfo,
1450 "World writable: '", pszPath, "'");
1451
1452 /*
1453 * Check the ACLs.
1454 */
1455 /** @todo */
1456
1457 return VINF_SUCCESS;
1458#endif
1459}
1460
1461
1462/**
1463 * Verifies that the file system object indicated by the native handle is the
1464 * same as the one @a pFsObjState indicates.
1465 *
1466 * @returns VBox status code, error buffer filled on failure.
1467 * @param hNative The native handle to the object @a pszPath
1468 * specifies and this should be verified to be the
1469 * same file system object.
1470 * @param pFsObjState The information/state returned by a previous
1471 * query call.
1472 * @param pszPath The path to the object @a pFsObjState
1473 * describes. (For the error message.)
1474 * @param pErrInfo The error info structure.
1475 */
1476static int supR3HardenedVerifySameFsObject(RTHCUINTPTR hNative, PCSUPR3HARDENEDFSOBJSTATE pFsObjState,
1477 const char *pszPath, PRTERRINFO pErrInfo)
1478{
1479 SUPR3HARDENEDFSOBJSTATE FsObjState2;
1480 int rc = supR3HardenedQueryFsObjectByHandle(hNative, &FsObjState2, pszPath, pErrInfo);
1481 if (RT_SUCCESS(rc))
1482 rc = supR3HardenedIsSameFsObject(pFsObjState, &FsObjState2, pszPath, pErrInfo);
1483 return rc;
1484}
1485
1486
1487/**
1488 * Does the recursive directory enumeration.
1489 *
1490 * @returns VBox status code, error buffer filled on failure.
1491 * @param pszDirPath The path buffer containing the subdirectory to
1492 * enumerate followed by a slash (this is never
1493 * the root slash). The buffer is RTPATH_MAX in
1494 * size and anything starting at @a cchDirPath
1495 * - 1 and beyond is scratch space.
1496 * @param cchDirPath The length of the directory path + slash.
1497 * @param pFsObjState Pointer to the file system object state buffer.
1498 * On input this will hold the stats for
1499 * the directory @a pszDirPath indicates and will
1500 * be used to verified that we're opening the same
1501 * thing.
1502 * @param fRecursive Whether to recurse into subdirectories.
1503 * @param pErrInfo The error info structure.
1504 */
1505static int supR3HardenedVerifyDirRecursive(char *pszDirPath, size_t cchDirPath, PSUPR3HARDENEDFSOBJSTATE pFsObjState,
1506 bool fRecursive, PRTERRINFO pErrInfo)
1507{
1508#if defined(RT_OS_WINDOWS)
1509 /** @todo Windows hardening. */
1510 return VINF_SUCCESS;
1511
1512#elif defined(RT_OS_OS2)
1513 /* No hardening here - it's a single user system. */
1514 return VINF_SUCCESS;
1515
1516#else
1517 /*
1518 * Open the directory. Now, we could probably eliminate opendir here
1519 * and go down on kernel API level (open + getdents for instance), however
1520 * that's not very portable and hopefully not necessary.
1521 */
1522 DIR *pDir = opendir(pszDirPath);
1523 if (!pDir)
1524 {
1525 /* Ignore access errors. */
1526 if (errno == EACCES)
1527 return VINF_SUCCESS;
1528 return supR3HardenedSetErrorN(VERR_SUPLIB_DIR_ENUM_FAILED, pErrInfo,
1529 5, "opendir failed with ", strerror(errno), " on '", pszDirPath, "'");
1530 }
1531 if (dirfd(pDir) != -1)
1532 {
1533 int rc = supR3HardenedVerifySameFsObject(dirfd(pDir), pFsObjState, pszDirPath, pErrInfo);
1534 if (RT_FAILURE(rc))
1535 {
1536 closedir(pDir);
1537 return rc;
1538 }
1539 }
1540
1541 /*
1542 * Enumerate the directory, check all the requested bits.
1543 */
1544 int rc = VINF_SUCCESS;
1545 for (;;)
1546 {
1547 pszDirPath[cchDirPath] = '\0'; /* for error messages. */
1548
1549 struct dirent Entry;
1550 struct dirent *pEntry;
1551 int iErr = readdir_r(pDir, &Entry, &pEntry);
1552 if (iErr)
1553 {
1554 rc = supR3HardenedSetErrorN(VERR_SUPLIB_DIR_ENUM_FAILED, pErrInfo,
1555 5, "readdir_r failed with ", strerror(iErr), " in '", pszDirPath, "'");
1556 break;
1557 }
1558 if (!pEntry)
1559 break;
1560
1561 /*
1562 * Check the length and copy it into the path buffer so it can be
1563 * stat()'ed.
1564 */
1565 size_t cchName = suplibHardenedStrLen(pEntry->d_name);
1566 if (cchName + cchDirPath > SUPR3HARDENED_MAX_PATH)
1567 {
1568 rc = supR3HardenedSetErrorN(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo,
1569 4, "Path grew too long during recursion: '", pszDirPath, pEntry->d_name, "'");
1570 break;
1571 }
1572 suplibHardenedMemCopy(&pszDirPath[cchName], pEntry->d_name, cchName + 1);
1573
1574 /*
1575 * Query the information about the entry and verify it.
1576 * (We don't bother skipping '.' and '..' at this point, a little bit
1577 * of extra checks doesn't hurt and neither requires relaxed handling.)
1578 */
1579 rc = supR3HardenedQueryFsObjectByPath(pszDirPath, pFsObjState, pErrInfo);
1580 if (RT_SUCCESS(rc))
1581 break;
1582 rc = supR3HardenedVerifyFsObject(pFsObjState, S_ISDIR(pFsObjState->Stat.st_mode), false /*fRelaxed*/,
1583 pszDirPath, pErrInfo);
1584 if (RT_FAILURE(rc))
1585 break;
1586
1587 /*
1588 * Recurse into subdirectories if requested.
1589 */
1590 if ( fRecursive
1591 && S_ISDIR(pFsObjState->Stat.st_mode)
1592 && suplibHardenedStrCmp(pEntry->d_name, ".")
1593 && suplibHardenedStrCmp(pEntry->d_name, ".."))
1594 {
1595 pszDirPath[cchDirPath + cchName] = RTPATH_SLASH;
1596 pszDirPath[cchDirPath + cchName + 1] = '\0';
1597
1598 rc = supR3HardenedVerifyDirRecursive(pszDirPath, cchDirPath + cchName + 1, pFsObjState,
1599 fRecursive, pErrInfo);
1600 if (RT_FAILURE(rc))
1601 break;
1602 }
1603 }
1604
1605 closedir(pDir);
1606 return VINF_SUCCESS;
1607#endif
1608}
1609
1610
1611/**
1612 * Worker for SUPR3HardenedVerifyDir.
1613 *
1614 * @returns See SUPR3HardenedVerifyDir.
1615 * @param pszDirPath See SUPR3HardenedVerifyDir.
1616 * @param fRecursive See SUPR3HardenedVerifyDir.
1617 * @param fCheckFiles See SUPR3HardenedVerifyDir.
1618 * @param pErrInfo See SUPR3HardenedVerifyDir.
1619 */
1620DECLHIDDEN(int) supR3HardenedVerifyDir(const char *pszDirPath, bool fRecursive, bool fCheckFiles, PRTERRINFO pErrInfo)
1621{
1622 /*
1623 * Validate the input path and parse it.
1624 */
1625 SUPR3HARDENEDPATHINFO Info;
1626 int rc = supR3HardenedVerifyPathSanity(pszDirPath, pErrInfo, &Info);
1627 if (RT_FAILURE(rc))
1628 return rc;
1629
1630 /*
1631 * Verify each component from the root up.
1632 */
1633 SUPR3HARDENEDFSOBJSTATE FsObjState;
1634 uint32_t const cComponents = Info.cComponents;
1635 for (uint32_t iComponent = 0; iComponent < cComponents; iComponent++)
1636 {
1637 bool fRelaxed = iComponent + 2 < cComponents;
1638 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0';
1639 rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo);
1640 if (RT_SUCCESS(rc))
1641 rc = supR3HardenedVerifyFsObject(&FsObjState, true /*fDir*/, fRelaxed, Info.szPath, pErrInfo);
1642 if (RT_FAILURE(rc))
1643 return rc;
1644 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = iComponent + 1 != cComponents ? RTPATH_SLASH : '\0';
1645 }
1646
1647 /*
1648 * Check files and subdirectories if requested.
1649 */
1650 if (fCheckFiles || fRecursive)
1651 {
1652 Info.szPath[Info.cch] = RTPATH_SLASH;
1653 Info.szPath[Info.cch + 1] = '\0';
1654 return supR3HardenedVerifyDirRecursive(Info.szPath, Info.cch + 1, &FsObjState,
1655 fRecursive, pErrInfo);
1656 }
1657
1658 return VINF_SUCCESS;
1659}
1660
1661
1662/**
1663 * Verfies a file.
1664 *
1665 * @returns VBox status code, error buffer filled on failure.
1666 * @param pszFilename The file to verify.
1667 * @param hNativeFile Handle to the file, verify that it's the same
1668 * as we ended up with when verifying the path.
1669 * RTHCUINTPTR_MAX means NIL here.
1670 * @param fMaybe3rdParty Set if the file is could be a supplied by a
1671 * third party. Different validation rules may
1672 * apply to 3rd party code on some platforms.
1673 * @param pErrInfo Where to return extended error information.
1674 * Optional.
1675 */
1676DECLHIDDEN(int) supR3HardenedVerifyFile(const char *pszFilename, RTHCUINTPTR hNativeFile,
1677 bool fMaybe3rdParty, PRTERRINFO pErrInfo)
1678{
1679 /*
1680 * Validate the input path and parse it.
1681 */
1682 SUPR3HARDENEDPATHINFO Info;
1683 int rc = supR3HardenedVerifyPathSanity(pszFilename, pErrInfo, &Info);
1684 if (RT_FAILURE(rc))
1685 return rc;
1686 if (Info.fDirSlash)
1687 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
1688 "The file path specifies a directory: '", pszFilename, "'");
1689
1690 /*
1691 * Verify each component from the root up.
1692 */
1693 SUPR3HARDENEDFSOBJSTATE FsObjState;
1694 uint32_t const cComponents = Info.cComponents;
1695 for (uint32_t iComponent = 0; iComponent < cComponents; iComponent++)
1696 {
1697 bool fFinal = iComponent + 1 == cComponents;
1698 bool fRelaxed = iComponent + 2 < cComponents;
1699 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0';
1700 rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo);
1701 if (RT_SUCCESS(rc))
1702 rc = supR3HardenedVerifyFsObject(&FsObjState, !fFinal /*fDir*/, fRelaxed, Info.szPath, pErrInfo);
1703 if (RT_FAILURE(rc))
1704 return rc;
1705 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = !fFinal ? RTPATH_SLASH : '\0';
1706 }
1707
1708 /*
1709 * Verify the file handle against the last component, if specified.
1710 */
1711 if (hNativeFile != RTHCUINTPTR_MAX)
1712 {
1713 rc = supR3HardenedVerifySameFsObject(hNativeFile, &FsObjState, Info.szPath, pErrInfo);
1714 if (RT_FAILURE(rc))
1715 return rc;
1716 }
1717
1718#ifdef RT_OS_WINDOWS
1719 /*
1720 * The files shall be signed on windows, verify that.
1721 */
1722 rc = VINF_SUCCESS;
1723 HANDLE hVerify;
1724 if (hNativeFile == RTHCUINTPTR_MAX)
1725 {
1726 PRTUTF16 pwszPath;
1727 rc = RTStrToUtf16(pszFilename, &pwszPath);
1728 if (RT_SUCCESS(rc))
1729 {
1730 hVerify = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1731 RTUtf16Free(pwszPath);
1732 }
1733 else
1734 rc = RTErrInfoSetF(pErrInfo, rc, "Error converting '%s' to UTF-16: %Rrc", pszFilename, rc);
1735 }
1736 else
1737 {
1738 NTSTATUS rcNt = NtDuplicateObject(NtCurrentProcess(), (HANDLE)hNativeFile, NtCurrentProcess(), &hVerify,
1739 GENERIC_READ, 0 /*HandleAttributes*/, 0 /*Options*/);
1740 if (!NT_SUCCESS(rcNt))
1741 hVerify = INVALID_HANDLE_VALUE;
1742 }
1743 if (hVerify != INVALID_HANDLE_VALUE)
1744 {
1745# ifdef VBOX_WITH_HARDENING
1746 uint32_t fFlags = SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING;
1747 if (!fMaybe3rdParty)
1748 fFlags = SUPHNTVI_F_REQUIRE_BUILD_CERT;
1749 const char *pszSuffix = RTPathSuffix(pszFilename);
1750 if ( pszSuffix
1751 && pszSuffix[0] == '.'
1752 && ( RT_C_TO_LOWER(pszSuffix[1]) == 'r'
1753 || RT_C_TO_LOWER(pszSuffix[1]) == 'g')
1754 && RT_C_TO_LOWER(pszSuffix[2]) == 'c'
1755 && pszSuffix[3] == '\0' )
1756 fFlags |= SUPHNTVI_F_RC_IMAGE;
1757# ifndef IN_SUP_R3_STATIC /* Not in VBoxCpuReport and friends. */
1758 rc = supHardenedWinVerifyImageByHandleNoName(hVerify, fFlags, pErrInfo);
1759# endif
1760# endif
1761 NtClose(hVerify);
1762 }
1763 else if (RT_SUCCESS(rc))
1764 rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(RtlGetLastWin32Error()),
1765 "Error %u trying to open (or duplicate handle for) '%s'", RtlGetLastWin32Error(), pszFilename);
1766 if (RT_FAILURE(rc))
1767 return rc;
1768#endif
1769
1770 return VINF_SUCCESS;
1771}
1772
1773
1774/**
1775 * Gets the pre-init data for the hand-over to the other version
1776 * of this code.
1777 *
1778 * The reason why we pass this information on is that it contains
1779 * open directories and files. Later it may include even more info
1780 * (int the verified arrays mostly).
1781 *
1782 * The receiver is supR3HardenedRecvPreInitData.
1783 *
1784 * @param pPreInitData Where to store it.
1785 */
1786DECLHIDDEN(void) supR3HardenedGetPreInitData(PSUPPREINITDATA pPreInitData)
1787{
1788 pPreInitData->cInstallFiles = RT_ELEMENTS(g_aSupInstallFiles);
1789 pPreInitData->paInstallFiles = &g_aSupInstallFiles[0];
1790 pPreInitData->paVerifiedFiles = &g_aSupVerifiedFiles[0];
1791
1792 pPreInitData->cVerifiedDirs = RT_ELEMENTS(g_aSupVerifiedDirs);
1793 pPreInitData->paVerifiedDirs = &g_aSupVerifiedDirs[0];
1794}
1795
1796
1797/**
1798 * Receives the pre-init data from the static executable stub.
1799 *
1800 * @returns VBox status code. Will not bitch on failure since the
1801 * runtime isn't ready for it, so that is left to the exe stub.
1802 *
1803 * @param pPreInitData The hand-over data.
1804 */
1805DECLHIDDEN(int) supR3HardenedRecvPreInitData(PCSUPPREINITDATA pPreInitData)
1806{
1807 /*
1808 * Compare the array lengths and the contents of g_aSupInstallFiles.
1809 */
1810 if ( pPreInitData->cInstallFiles != RT_ELEMENTS(g_aSupInstallFiles)
1811 || pPreInitData->cVerifiedDirs != RT_ELEMENTS(g_aSupVerifiedDirs))
1812 return VERR_VERSION_MISMATCH;
1813 SUPINSTFILE const *paInstallFiles = pPreInitData->paInstallFiles;
1814 for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++)
1815 if ( g_aSupInstallFiles[iFile].enmDir != paInstallFiles[iFile].enmDir
1816 || g_aSupInstallFiles[iFile].enmType != paInstallFiles[iFile].enmType
1817 || g_aSupInstallFiles[iFile].fOptional != paInstallFiles[iFile].fOptional
1818 || suplibHardenedStrCmp(g_aSupInstallFiles[iFile].pszFile, paInstallFiles[iFile].pszFile))
1819 return VERR_VERSION_MISMATCH;
1820
1821 /*
1822 * Check that we're not called out of order.
1823 * If dynamic linking it screwed up, we may end up here.
1824 */
1825 if ( !ASMMemIsZero(&g_aSupVerifiedFiles[0], sizeof(g_aSupVerifiedFiles))
1826 || !ASMMemIsZero(&g_aSupVerifiedDirs[0], sizeof(g_aSupVerifiedDirs)))
1827 return VERR_WRONG_ORDER;
1828
1829 /*
1830 * Copy the verification data over.
1831 */
1832 suplibHardenedMemCopy(&g_aSupVerifiedFiles[0], pPreInitData->paVerifiedFiles, sizeof(g_aSupVerifiedFiles));
1833 suplibHardenedMemCopy(&g_aSupVerifiedDirs[0], pPreInitData->paVerifiedDirs, sizeof(g_aSupVerifiedDirs));
1834 return VINF_SUCCESS;
1835}
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