VirtualBox

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

Last change on this file since 76568 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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