VirtualBox

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

Last change on this file since 81784 was 81158, checked in by vboxsync, 5 years ago

SUP: Removed most VBOX_WITH_REM preprocessor stuff. bugref:9576

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 83.4 KB
Line 
1/* $Id: SUPR3HardenedVerify.cpp 81158 2019-10-08 15:01:41Z 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#if HC_ARCH_BITS == 32
134 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxREM32" SUPLIB_DLL_SUFF },
135 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxREM64" SUPLIB_DLL_SUFF },
136#endif
137 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxDD" SUPLIB_DLL_SUFF },
138 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxDD2" SUPLIB_DLL_SUFF },
139 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxDDU" SUPLIB_DLL_SUFF },
140 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxVMMPreload" SUPLIB_EXE_SUFF },
141 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxVMMPreload" SUPLIB_DLL_SUFF },
142
143//#ifdef VBOX_WITH_DEBUGGER_GUI
144 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxDbg" SUPLIB_DLL_SUFF },
145 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxDbg3" SUPLIB_DLL_SUFF },
146//#endif
147
148//#ifdef VBOX_WITH_SHARED_CLIPBOARD
149 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSharedClipboard" SUPLIB_DLL_SUFF },
150//#endif
151//#ifdef VBOX_WITH_SHARED_FOLDERS
152 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSharedFolders" SUPLIB_DLL_SUFF },
153//#endif
154//#ifdef VBOX_WITH_DRAG_AND_DROP
155 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxDragAndDropSvc" SUPLIB_DLL_SUFF },
156//#endif
157//#ifdef VBOX_WITH_GUEST_PROPS
158 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxGuestPropSvc" SUPLIB_DLL_SUFF },
159//#endif
160//#ifdef VBOX_WITH_GUEST_CONTROL
161 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxGuestControlSvc" SUPLIB_DLL_SUFF },
162//#endif
163 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxHostChannel" SUPLIB_DLL_SUFF },
164 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSharedCrOpenGL" SUPLIB_DLL_SUFF },
165 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxOGLhostcrutil" SUPLIB_DLL_SUFF },
166 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxOGLhosterrorspu" SUPLIB_DLL_SUFF },
167 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxOGLrenderspu" SUPLIB_DLL_SUFF },
168
169 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxManage" SUPLIB_EXE_SUFF },
170
171#ifdef VBOX_WITH_MAIN
172 { kSupIFT_Exe, kSupID_AppBin, false, "VBoxSVC" SUPLIB_EXE_SUFF },
173 #ifdef RT_OS_WINDOWS
174 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxC" SUPLIB_DLL_SUFF },
175 #else
176 { kSupIFT_Exe, kSupID_AppPrivArch, false, "VBoxXPCOMIPCD" SUPLIB_EXE_SUFF },
177 { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxXPCOM" SUPLIB_DLL_SUFF },
178 { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxXPCOMIPCC" SUPLIB_DLL_SUFF },
179 { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxC" SUPLIB_DLL_SUFF },
180 { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxSVCM" SUPLIB_DLL_SUFF },
181 { kSupIFT_Data, kSupID_AppPrivArchComp, false, "VBoxXPCOMBase.xpt" },
182 #endif
183#endif
184
185 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VRDPAuth" SUPLIB_DLL_SUFF },
186 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxAuth" SUPLIB_DLL_SUFF },
187 { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxVRDP" SUPLIB_DLL_SUFF },
188
189//#ifdef VBOX_WITH_HEADLESS
190 { kSupIFT_Exe, kSupID_AppBin, true, "VBoxHeadless" SUPLIB_EXE_SUFF },
191 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxHeadless" SUPLIB_DLL_SUFF },
192 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxVideoRecFB" SUPLIB_DLL_SUFF },
193//#endif
194
195//#ifdef VBOX_WITH_QTGUI
196 { kSupIFT_Exe, kSupID_AppBin, true, "VirtualBox" SUPLIB_EXE_SUFF },
197 { kSupIFT_Exe, kSupID_AppBin, true, "VirtualBoxVM" SUPLIB_EXE_SUFF },
198 { kSupIFT_Dll, kSupID_AppPrivArch, true, "VirtualBoxVM" SUPLIB_DLL_SUFF },
199 { kSupIFT_Dll, kSupID_AppPrivArch, true, "UICommon" SUPLIB_DLL_SUFF },
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 RT_NOREF4(pFile, pVerified, fFatal, fLeaveFileOpen);
621 return VINF_SUCCESS;
622# endif /* Not checking signatures. */
623}
624#endif
625
626
627/**
628 * Verifies a file entry.
629 *
630 * @returns VINF_SUCCESS on success. On failure, an error code is returned if
631 * fFatal is clear and if it's set the function wont return.
632 *
633 * @param iFile The file table index of the file to be verified.
634 * @param fFatal Whether validation failures should be treated as
635 * fatal (true) or not (false).
636 * @param fLeaveFileOpen Whether the file should be left open.
637 * @param fVerifyAll Set if this is an verify all call and we will
638 * postpone signature checking.
639 */
640static int supR3HardenedVerifyFileInternal(int iFile, bool fFatal, bool fLeaveFileOpen, bool fVerifyAll)
641{
642#ifndef RT_OS_WINDOWS
643 RT_NOREF1(fVerifyAll);
644#endif
645 PCSUPINSTFILE pFile = &g_aSupInstallFiles[iFile];
646 PSUPVERIFIEDFILE pVerified = &g_aSupVerifiedFiles[iFile];
647
648 /*
649 * Already done validation? Do signature validation if we haven't yet.
650 */
651 if (pVerified->fValidated)
652 {
653 /** @todo revalidate? Check that the file hasn't been replace or similar. */
654#ifdef RT_OS_WINDOWS
655 if (!pVerified->fCheckedSignature && !fVerifyAll)
656 return supR3HardenedVerifyFileSignature(pFile, pVerified, fFatal, fLeaveFileOpen);
657#endif
658 return VINF_SUCCESS;
659 }
660
661
662 /* initialize the entry. */
663 if (pVerified->hFile != 0)
664 supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
665 "supR3HardenedVerifyFileInternal: hFile=%p (%s)\n",
666 (void *)pVerified->hFile, pFile->pszFile);
667 pVerified->hFile = -1;
668 pVerified->fValidated = false;
669#ifdef RT_OS_WINDOWS
670 pVerified->fCheckedSignature = false;
671#endif
672
673 /*
674 * Verify the directory then proceed to open it.
675 * (This'll make sure the directory is opened and that we can (later)
676 * use openat if we wish.)
677 */
678 int rc = supR3HardenedVerifyFixedDir(pFile->enmDir, fFatal);
679 if (RT_SUCCESS(rc))
680 {
681#if defined(RT_OS_WINDOWS)
682 rc = supR3HardenedVerifyFileOpen(pFile, fFatal, &pVerified->hFile);
683 if (RT_SUCCESS(rc))
684 {
685 if (!fVerifyAll)
686 rc = supR3HardenedVerifyFileSignature(pFile, pVerified, fFatal, fLeaveFileOpen);
687 if (RT_SUCCESS(rc))
688 {
689 pVerified->fValidated = true;
690 if (!fLeaveFileOpen)
691 {
692 NtClose((HANDLE)pVerified->hFile);
693 pVerified->hFile = -1;
694 }
695 }
696 }
697#else /* !RT_OS_WINDOWS */
698 char szPath[RTPATH_MAX];
699 rc = supR3HardenedMakeFilePath(pFile, szPath, sizeof(szPath), true /*fWithFilename*/, fFatal);
700 if (RT_SUCCESS(rc))
701 {
702 int fd = open(szPath, O_RDONLY, 0);
703 if (fd >= 0)
704 {
705 /*
706 * On unixy systems we'll make sure the file is owned by root
707 * and not writable by the group and user.
708 */
709 struct stat st;
710 if (!fstat(fd, &st))
711 {
712 if ( st.st_uid == 0
713 && !(st.st_mode & (S_IWGRP | S_IWOTH))
714 && S_ISREG(st.st_mode))
715 {
716 /* it's valid. */
717 if (fLeaveFileOpen)
718 pVerified->hFile = fd;
719 else
720 close(fd);
721 pVerified->fValidated = true;
722 }
723 else
724 {
725 if (!S_ISREG(st.st_mode))
726 rc = supR3HardenedError(VERR_IS_A_DIRECTORY, fFatal,
727 "supR3HardenedVerifyFileInternal: \"%s\" is not a regular file\n",
728 szPath, (long)st.st_uid);
729 else if (st.st_uid)
730 rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal,
731 "supR3HardenedVerifyFileInternal: Cannot trust the file \"%s\": not owned by root (st_uid=%ld)\n",
732 szPath, (long)st.st_uid);
733 else
734 rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal,
735 "supR3HardenedVerifyFileInternal: Cannot trust the file \"%s\": group and/or other writable (st_mode=0%lo)\n",
736 szPath, (long)st.st_mode);
737 close(fd);
738 }
739 }
740 else
741 {
742 int err = errno;
743 rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal,
744 "supR3HardenedVerifyFileInternal: Failed to fstat \"%s\": %s (%d)\n",
745 szPath, strerror(err), err);
746 close(fd);
747 }
748 }
749 else
750 {
751 int err = errno;
752 if (!pFile->fOptional || err != ENOENT)
753 rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal,
754 "supR3HardenedVerifyFileInternal: Failed to open \"%s\": %s (%d)\n",
755 szPath, strerror(err), err);
756 }
757 }
758#endif /* !RT_OS_WINDOWS */
759 }
760
761 return rc;
762}
763
764
765/**
766 * Verifies that the specified table entry matches the given filename.
767 *
768 * @returns VINF_SUCCESS if matching. On mismatch fFatal indicates whether an
769 * error is returned or we terminate the application.
770 *
771 * @param iFile The file table index.
772 * @param pszFilename The filename.
773 * @param fFatal Whether validation failures should be treated as
774 * fatal (true) or not (false).
775 */
776static int supR3HardenedVerifySameFile(int iFile, const char *pszFilename, bool fFatal)
777{
778 PCSUPINSTFILE pFile = &g_aSupInstallFiles[iFile];
779
780 /*
781 * Construct the full path for the file table entry
782 * and compare it with the specified file.
783 */
784 char szName[RTPATH_MAX];
785 int rc = supR3HardenedMakeFilePath(pFile, szName, sizeof(szName), true /*fWithFilename*/, fFatal);
786 if (RT_FAILURE(rc))
787 return rc;
788 if (SUP_COMP_FILENAME(szName, pszFilename))
789 {
790 /*
791 * Normalize the two paths and compare again.
792 */
793 rc = VERR_NOT_SAME_DEVICE;
794#if defined(RT_OS_WINDOWS)
795 LPSTR pszIgnored;
796 char szName2[RTPATH_MAX]; /** @todo Must use UTF-16 here! Code is mixing UTF-8 and native. */
797 if ( GetFullPathName(szName, RT_ELEMENTS(szName2), &szName2[0], &pszIgnored)
798 && GetFullPathName(pszFilename, RT_ELEMENTS(szName), &szName[0], &pszIgnored))
799 if (!SUP_COMP_FILENAME(szName2, szName))
800 rc = VINF_SUCCESS;
801#else
802 AssertCompile(RTPATH_MAX >= PATH_MAX);
803 char szName2[RTPATH_MAX];
804 if ( realpath(szName, szName2) != NULL
805 && realpath(pszFilename, szName) != NULL)
806 if (!SUP_COMP_FILENAME(szName2, szName))
807 rc = VINF_SUCCESS;
808#endif
809
810 if (RT_FAILURE(rc))
811 {
812 supR3HardenedMakeFilePath(pFile, szName, sizeof(szName), true /*fWithFilename*/, fFatal);
813 return supR3HardenedError(rc, fFatal,
814 "supR3HardenedVerifySameFile: \"%s\" isn't the same as \"%s\"\n",
815 pszFilename, szName);
816 }
817 }
818
819 /*
820 * Check more stuff like the stat info if it's an already open file?
821 */
822
823
824
825 return VINF_SUCCESS;
826}
827
828
829/**
830 * Verifies a file.
831 *
832 * @returns VINF_SUCCESS on success.
833 * VERR_NOT_FOUND if the file isn't in the table, this isn't ever a fatal error.
834 * On verification failure, an error code will be returned when fFatal is clear,
835 * otherwise the program will be terminated.
836 *
837 * @param pszFilename The filename.
838 * @param fFatal Whether validation failures should be treated as
839 * fatal (true) or not (false).
840 */
841DECLHIDDEN(int) supR3HardenedVerifyFixedFile(const char *pszFilename, bool fFatal)
842{
843 /*
844 * Lookup the file and check if it's the same file.
845 */
846 const char *pszName = supR3HardenedPathFilename(pszFilename);
847 for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++)
848 if (!SUP_COMP_FILENAME(pszName, g_aSupInstallFiles[iFile].pszFile))
849 {
850 int rc = supR3HardenedVerifySameFile(iFile, pszFilename, fFatal);
851 if (RT_SUCCESS(rc))
852 rc = supR3HardenedVerifyFileInternal(iFile, fFatal, false /* fLeaveFileOpen */, false /* fVerifyAll */);
853 return rc;
854 }
855
856 return VERR_NOT_FOUND;
857}
858
859
860/**
861 * Verifies a program, worker for supR3HardenedVerifyAll.
862 *
863 * @returns See supR3HardenedVerifyAll.
864 * @param pszProgName See supR3HardenedVerifyAll.
865 * @param pszExePath The path to the executable.
866 * @param fFatal See supR3HardenedVerifyAll.
867 * @param fLeaveOpen The leave open setting used by
868 * supR3HardenedVerifyAll.
869 * @param fMainFlags Flags supplied to SUPR3HardenedMain.
870 */
871static int supR3HardenedVerifyProgram(const char *pszProgName, const char *pszExePath, bool fFatal,
872 bool fLeaveOpen, uint32_t fMainFlags)
873{
874 /*
875 * Search the table looking for the executable and the DLL/DYLIB/SO.
876 * Note! On darwin we have a hack in place for VirtualBoxVM helper app
877 * to share VirtualBox.dylib with the VirtualBox app. This ASSUMES
878 * that cchProgNameDll is equal or shorter to the exe name.
879 */
880 int rc = VINF_SUCCESS;
881 bool fExe = false;
882 bool fDll = false;
883 size_t const cchProgNameExe = suplibHardenedStrLen(pszProgName);
884#ifndef RT_OS_DARWIN
885 size_t const cchProgNameDll = cchProgNameExe;
886 NOREF(fMainFlags);
887#else
888 size_t const cchProgNameDll = fMainFlags & SUPSECMAIN_FLAGS_OSX_VM_APP
889 ? sizeof("VirtualBox") - 1
890 : cchProgNameExe;
891 if (cchProgNameDll > cchProgNameExe)
892 return supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
893 "supR3HardenedVerifyProgram: SUPSECMAIN_FLAGS_OSX_VM_APP + '%s'", pszProgName);
894#endif
895 for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++)
896 if (!suplibHardenedStrNCmp(pszProgName, g_aSupInstallFiles[iFile].pszFile, cchProgNameDll))
897 {
898 if ( ( g_aSupInstallFiles[iFile].enmType == kSupIFT_Dll
899 || g_aSupInstallFiles[iFile].enmType == kSupIFT_TestDll)
900 && !suplibHardenedStrCmp(&g_aSupInstallFiles[iFile].pszFile[cchProgNameDll], SUPLIB_DLL_SUFF))
901 {
902 /* This only has to be found (once). */
903 if (fDll)
904 rc = supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
905 "supR3HardenedVerifyProgram: duplicate DLL entry for \"%s\"\n", pszProgName);
906 else
907 rc = supR3HardenedVerifyFileInternal(iFile, fFatal, fLeaveOpen,
908 true /* fVerifyAll - check sign later, only final process need check it on load. */);
909 fDll = true;
910 }
911 else if ( ( g_aSupInstallFiles[iFile].enmType == kSupIFT_Exe
912 || g_aSupInstallFiles[iFile].enmType == kSupIFT_TestExe)
913 && ( cchProgNameExe == cchProgNameDll
914 || !suplibHardenedStrNCmp(pszProgName, g_aSupInstallFiles[iFile].pszFile, cchProgNameExe))
915 && !suplibHardenedStrCmp(&g_aSupInstallFiles[iFile].pszFile[cchProgNameExe], SUPLIB_EXE_SUFF))
916 {
917 /* Here we'll have to check that the specific program is the same as the entry. */
918 if (fExe)
919 rc = supR3HardenedError(VERR_INTERNAL_ERROR, fFatal,
920 "supR3HardenedVerifyProgram: duplicate EXE entry for \"%s\"\n", pszProgName);
921 else
922 rc = supR3HardenedVerifyFileInternal(iFile, fFatal, fLeaveOpen, false /* fVerifyAll */);
923 fExe = true;
924
925 supR3HardenedVerifySameFile(iFile, pszExePath, fFatal);
926 }
927 }
928
929 /*
930 * Check the findings.
931 */
932 if (RT_SUCCESS(rc))
933 {
934 if (!fDll && !fExe)
935 rc = supR3HardenedError(VERR_NOT_FOUND, fFatal,
936 "supR3HardenedVerifyProgram: Couldn't find the program \"%s\"\n", pszProgName);
937 else if (!fExe)
938 rc = supR3HardenedError(VERR_NOT_FOUND, fFatal,
939 "supR3HardenedVerifyProgram: Couldn't find the EXE entry for \"%s\"\n", pszProgName);
940 else if (!fDll)
941 rc = supR3HardenedError(VERR_NOT_FOUND, fFatal,
942 "supR3HardenedVerifyProgram: Couldn't find the DLL entry for \"%s\"\n", pszProgName);
943 }
944 return rc;
945}
946
947
948/**
949 * Verifies all the known files (called from SUPR3HardenedMain).
950 *
951 * @returns VINF_SUCCESS on success.
952 * On verification failure, an error code will be returned when fFatal is clear,
953 * otherwise the program will be terminated.
954 *
955 * @param fFatal Whether validation failures should be treated as
956 * fatal (true) or not (false).
957 * @param pszProgName The program name. This is used to verify that
958 * both the executable and corresponding
959 * DLL/DYLIB/SO are valid.
960 * @param pszExePath The path to the executable.
961 * @param fMainFlags Flags supplied to SUPR3HardenedMain.
962 */
963DECLHIDDEN(int) supR3HardenedVerifyAll(bool fFatal, const char *pszProgName, const char *pszExePath, uint32_t fMainFlags)
964{
965 /*
966 * On windows
967 */
968#if defined(RT_OS_WINDOWS)
969 bool fLeaveOpen = true;
970#else
971 bool fLeaveOpen = false;
972#endif
973
974 /*
975 * The verify all the files.
976 */
977 int rc = VINF_SUCCESS;
978 for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++)
979 {
980 int rc2 = supR3HardenedVerifyFileInternal(iFile, fFatal, fLeaveOpen, true /* fVerifyAll */);
981 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
982 rc = rc2;
983 }
984
985 /*
986 * Verify the program name, that is to say, check that it's in the table
987 * (thus verified above) and verify the signature on platforms where we
988 * sign things.
989 */
990 int rc2 = supR3HardenedVerifyProgram(pszProgName, pszExePath, fFatal, fLeaveOpen, fMainFlags);
991 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
992 rc2 = rc;
993
994 return rc;
995}
996
997
998/**
999 * Copies the N messages into the error buffer and returns @a rc.
1000 *
1001 * @returns Returns @a rc
1002 * @param rc The return code.
1003 * @param pErrInfo The error info structure.
1004 * @param cMsgs The number of messages in the ellipsis.
1005 * @param ... Message parts.
1006 */
1007static int supR3HardenedSetErrorN(int rc, PRTERRINFO pErrInfo, unsigned cMsgs, ...)
1008{
1009 if (pErrInfo)
1010 {
1011 size_t cbErr = pErrInfo->cbMsg;
1012 char *pszErr = pErrInfo->pszMsg;
1013
1014 va_list va;
1015 va_start(va, cMsgs);
1016 while (cMsgs-- > 0 && cbErr > 0)
1017 {
1018 const char *pszMsg = va_arg(va, const char *);
1019 size_t cchMsg = VALID_PTR(pszMsg) ? suplibHardenedStrLen(pszMsg) : 0;
1020 if (cchMsg >= cbErr)
1021 cchMsg = cbErr - 1;
1022 suplibHardenedMemCopy(pszErr, pszMsg, cchMsg);
1023 pszErr[cchMsg] = '\0';
1024 pszErr += cchMsg;
1025 cbErr -= cchMsg;
1026 }
1027 va_end(va);
1028
1029 pErrInfo->rc = rc;
1030 pErrInfo->fFlags |= RTERRINFO_FLAGS_SET;
1031 }
1032
1033 return rc;
1034}
1035
1036
1037#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX)
1038/**
1039 * Copies the four messages into the error buffer and returns @a rc.
1040 *
1041 * @returns Returns @a rc
1042 * @param rc The return code.
1043 * @param pErrInfo The error info structure.
1044 * @param pszMsg1 The first message part.
1045 * @param pszMsg2 The second message part.
1046 * @param pszMsg3 The third message part.
1047 * @param pszMsg4 The fourth message part.
1048 */
1049static int supR3HardenedSetError4(int rc, PRTERRINFO pErrInfo, const char *pszMsg1,
1050 const char *pszMsg2, const char *pszMsg3, const char *pszMsg4)
1051{
1052 return supR3HardenedSetErrorN(rc, pErrInfo, 4, pszMsg1, pszMsg2, pszMsg3, pszMsg4);
1053}
1054#endif
1055
1056
1057/**
1058 * Copies the three messages into the error buffer and returns @a rc.
1059 *
1060 * @returns Returns @a rc
1061 * @param rc The return code.
1062 * @param pErrInfo The error info structure.
1063 * @param pszMsg1 The first message part.
1064 * @param pszMsg2 The second message part.
1065 * @param pszMsg3 The third message part.
1066 */
1067static int supR3HardenedSetError3(int rc, PRTERRINFO pErrInfo, const char *pszMsg1,
1068 const char *pszMsg2, const char *pszMsg3)
1069{
1070 return supR3HardenedSetErrorN(rc, pErrInfo, 3, pszMsg1, pszMsg2, pszMsg3);
1071}
1072
1073
1074#ifdef SOME_UNUSED_FUNCTION
1075/**
1076 * Copies the two messages into the error buffer and returns @a rc.
1077 *
1078 * @returns Returns @a rc
1079 * @param rc The return code.
1080 * @param pErrInfo The error info structure.
1081 * @param pszMsg1 The first message part.
1082 * @param pszMsg2 The second message part.
1083 */
1084static int supR3HardenedSetError2(int rc, PRTERRINFO pErrInfo, const char *pszMsg1,
1085 const char *pszMsg2)
1086{
1087 return supR3HardenedSetErrorN(rc, pErrInfo, 2, pszMsg1, pszMsg2);
1088}
1089#endif
1090
1091
1092#ifndef SUP_HARDENED_VERIFY_FOLLOW_SYMLINKS_USE_REALPATH
1093# if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX)
1094/**
1095 * Copies the error message to the error buffer and returns @a rc.
1096 *
1097 * @returns Returns @a rc
1098 * @param rc The return code.
1099 * @param pErrInfo The error info structure.
1100 * @param pszMsg The message.
1101 */
1102static int supR3HardenedSetError(int rc, PRTERRINFO pErrInfo, const char *pszMsg)
1103{
1104 return supR3HardenedSetErrorN(rc, pErrInfo, 1, pszMsg);
1105}
1106# endif
1107#endif
1108
1109
1110/**
1111 * Output from a successfull supR3HardenedVerifyPathSanity call.
1112 */
1113typedef struct SUPR3HARDENEDPATHINFO
1114{
1115 /** The length of the path in szCopy. */
1116 uint16_t cch;
1117 /** The number of path components. */
1118 uint16_t cComponents;
1119 /** Set if the path ends with slash, indicating that it's a directory
1120 * reference and not a file reference. The slash has been removed from
1121 * the copy. */
1122 bool fDirSlash;
1123 /** The offset where each path component starts, i.e. the char after the
1124 * slash. The array has cComponents + 1 entries, where the final one is
1125 * cch + 1 so that one can always terminate the current component by
1126 * szPath[aoffComponent[i] - 1] = '\0'. */
1127 uint16_t aoffComponents[32+1];
1128 /** A normalized copy of the path.
1129 * Reserve some extra space so we can be more relaxed about overflow
1130 * checks and terminator paddings, especially when recursing. */
1131 char szPath[SUPR3HARDENED_MAX_PATH * 2];
1132} SUPR3HARDENEDPATHINFO;
1133/** Pointer to a parsed path. */
1134typedef SUPR3HARDENEDPATHINFO *PSUPR3HARDENEDPATHINFO;
1135
1136
1137/**
1138 * Verifies that the path is absolutely sane, it also parses the path.
1139 *
1140 * A sane path starts at the root (w/ drive letter on DOS derived systems) and
1141 * does not have any relative bits (/../) or unnecessary slashes (/bin//ls).
1142 * Sane paths are less or equal to SUPR3HARDENED_MAX_PATH bytes in length. UNC
1143 * paths are not supported.
1144 *
1145 * @returns VBox status code.
1146 * @param pszPath The path to check.
1147 * @param pErrInfo The error info structure.
1148 * @param pInfo Where to return a copy of the path along with
1149 * parsing information.
1150 */
1151static int supR3HardenedVerifyPathSanity(const char *pszPath, PRTERRINFO pErrInfo, PSUPR3HARDENEDPATHINFO pInfo)
1152{
1153 const char *pszSrc = pszPath;
1154 char *pszDst = pInfo->szPath;
1155
1156 /*
1157 * Check that it's an absolute path and copy the volume/root specifier.
1158 */
1159#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1160 if ( !RT_C_IS_ALPHA(pszSrc[0])
1161 || pszSrc[1] != ':'
1162 || !RTPATH_IS_SLASH(pszSrc[2]))
1163 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo, "The path is not absolute: '", pszPath, "'");
1164
1165 *pszDst++ = RT_C_TO_UPPER(pszSrc[0]);
1166 *pszDst++ = ':';
1167 *pszDst++ = RTPATH_SLASH;
1168 pszSrc += 3;
1169
1170#else
1171 if (!RTPATH_IS_SLASH(pszSrc[0]))
1172 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo, "The path is not absolute: '", pszPath, "'");
1173
1174 *pszDst++ = RTPATH_SLASH;
1175 pszSrc += 1;
1176#endif
1177
1178 /*
1179 * No path specifying the root or something very shortly thereafter will
1180 * be approved of.
1181 */
1182 if (pszSrc[0] == '\0')
1183 return supR3HardenedSetError3(VERR_SUPLIB_PATH_IS_ROOT, pErrInfo, "The path is root: '", pszPath, "'");
1184 if ( pszSrc[1] == '\0'
1185 || pszSrc[2] == '\0')
1186 return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_SHORT, pErrInfo, "The path is too short: '", pszPath, "'");
1187
1188#if RTPATH_STYLE == RTPATH_STR_F_STYLE_UNIX
1189 /*
1190 * Skip double slashes.
1191 */
1192 while (RTPATH_IS_SLASH(*pszSrc))
1193 pszSrc++;
1194#else
1195 /*
1196 * The root slash should be alone to avoid UNC confusion.
1197 */
1198 if (RTPATH_IS_SLASH(pszSrc[0]))
1199 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_CLEAN, pErrInfo,
1200 "The path is not clean of leading double slashes: '", pszPath, "'");
1201#endif
1202 /*
1203 * Check each component. No parent references.
1204 */
1205 pInfo->cComponents = 0;
1206 pInfo->fDirSlash = false;
1207 while (pszSrc[0])
1208 {
1209 /* Sanity checks. */
1210 if ( pszSrc[0] == '.'
1211 && pszSrc[1] == '.'
1212 && RTPATH_IS_SLASH(pszSrc[2]))
1213 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo,
1214 "The path is not absolute: '", pszPath, "'");
1215
1216 /* Record the start of the component. */
1217 if (pInfo->cComponents >= RT_ELEMENTS(pInfo->aoffComponents) - 1)
1218 return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_MANY_COMPONENTS, pErrInfo,
1219 "The path has too many components: '", pszPath, "'");
1220 pInfo->aoffComponents[pInfo->cComponents++] = pszDst - &pInfo->szPath[0];
1221
1222 /* Traverse to the end of the component, copying it as we go along. */
1223 while (pszSrc[0])
1224 {
1225 if (RTPATH_IS_SLASH(pszSrc[0]))
1226 {
1227 pszSrc++;
1228 if (*pszSrc)
1229 *pszDst++ = RTPATH_SLASH;
1230 else
1231 pInfo->fDirSlash = true;
1232 break;
1233 }
1234 *pszDst++ = *pszSrc++;
1235 if ((uintptr_t)(pszDst - &pInfo->szPath[0]) >= SUPR3HARDENED_MAX_PATH)
1236 return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo,
1237 "The path is too long: '", pszPath, "'");
1238 }
1239
1240 /* Skip double slashes. */
1241 while (RTPATH_IS_SLASH(*pszSrc))
1242 pszSrc++;
1243 }
1244
1245 /* Terminate the string and enter its length. */
1246 pszDst[0] = '\0';
1247 pszDst[1] = '\0'; /* for aoffComponents */
1248 pInfo->cch = (uint16_t)(pszDst - &pInfo->szPath[0]);
1249 pInfo->aoffComponents[pInfo->cComponents] = pInfo->cch + 1;
1250
1251 return VINF_SUCCESS;
1252}
1253
1254
1255/**
1256 * The state information collected by supR3HardenedVerifyFsObject.
1257 *
1258 * This can be used to verify that a directory we've opened for enumeration is
1259 * the same as the one that supR3HardenedVerifyFsObject just verified. It can
1260 * equally be used to verify a native specfied by the user.
1261 */
1262typedef struct SUPR3HARDENEDFSOBJSTATE
1263{
1264#ifdef RT_OS_WINDOWS
1265 /** Not implemented for windows yet. */
1266 char chTodo;
1267#else
1268 /** The stat output. */
1269 struct stat Stat;
1270#endif
1271} SUPR3HARDENEDFSOBJSTATE;
1272/** Pointer to a file system object state. */
1273typedef SUPR3HARDENEDFSOBJSTATE *PSUPR3HARDENEDFSOBJSTATE;
1274/** Pointer to a const file system object state. */
1275typedef SUPR3HARDENEDFSOBJSTATE const *PCSUPR3HARDENEDFSOBJSTATE;
1276
1277
1278/**
1279 * Query information about a file system object by path.
1280 *
1281 * @returns VBox status code, error buffer filled on failure.
1282 * @param pszPath The path to the object.
1283 * @param pFsObjState Where to return the state information.
1284 * @param pErrInfo The error info structure.
1285 */
1286static int supR3HardenedQueryFsObjectByPath(char const *pszPath, PSUPR3HARDENEDFSOBJSTATE pFsObjState, PRTERRINFO pErrInfo)
1287{
1288#if defined(RT_OS_WINDOWS)
1289 /** @todo Windows hardening. */
1290 pFsObjState->chTodo = 0;
1291 RT_NOREF2(pszPath, pErrInfo);
1292 return VINF_SUCCESS;
1293
1294#else
1295 /*
1296 * Stat the object, do not follow links.
1297 */
1298 if (lstat(pszPath, &pFsObjState->Stat) != 0)
1299 {
1300 /* Ignore access errors */
1301 if (errno != EACCES)
1302 return supR3HardenedSetErrorN(VERR_SUPLIB_STAT_FAILED, pErrInfo,
1303 5, "stat failed with ", strerror(errno), " on: '", pszPath, "'");
1304 }
1305
1306 /*
1307 * Read ACLs.
1308 */
1309 /** @todo */
1310
1311 return VINF_SUCCESS;
1312#endif
1313}
1314
1315
1316/**
1317 * Query information about a file system object by native handle.
1318 *
1319 * @returns VBox status code, error buffer filled on failure.
1320 * @param hNative The native handle to the object @a pszPath
1321 * specifies and this should be verified to be the
1322 * same file system object.
1323 * @param pFsObjState Where to return the state information.
1324 * @param pszPath The path to the object. (For the error message
1325 * only.)
1326 * @param pErrInfo The error info structure.
1327 */
1328static int supR3HardenedQueryFsObjectByHandle(RTHCUINTPTR hNative, PSUPR3HARDENEDFSOBJSTATE pFsObjState,
1329 char const *pszPath, PRTERRINFO pErrInfo)
1330{
1331#if defined(RT_OS_WINDOWS)
1332 /** @todo Windows hardening. */
1333 pFsObjState->chTodo = 0;
1334 RT_NOREF3(hNative, pszPath, pErrInfo);
1335 return VINF_SUCCESS;
1336
1337#else
1338 /*
1339 * Stat the object, do not follow links.
1340 */
1341 if (fstat((int)hNative, &pFsObjState->Stat) != 0)
1342 return supR3HardenedSetErrorN(VERR_SUPLIB_STAT_FAILED, pErrInfo,
1343 5, "fstat failed with ", strerror(errno), " on '", pszPath, "'");
1344
1345 /*
1346 * Read ACLs.
1347 */
1348 /** @todo */
1349
1350 return VINF_SUCCESS;
1351#endif
1352}
1353
1354
1355/**
1356 * Verifies that the file system object indicated by the native handle is the
1357 * same as the one @a pFsObjState indicates.
1358 *
1359 * @returns VBox status code, error buffer filled on failure.
1360 * @param pFsObjState1 File system object information/state by path.
1361 * @param pFsObjState2 File system object information/state by handle.
1362 * @param pszPath The path to the object @a pFsObjState
1363 * describes. (For the error message.)
1364 * @param pErrInfo The error info structure.
1365 */
1366static int supR3HardenedIsSameFsObject(PCSUPR3HARDENEDFSOBJSTATE pFsObjState1, PCSUPR3HARDENEDFSOBJSTATE pFsObjState2,
1367 const char *pszPath, PRTERRINFO pErrInfo)
1368{
1369#if defined(RT_OS_WINDOWS)
1370 /** @todo Windows hardening. */
1371 RT_NOREF4(pFsObjState1, pFsObjState2, pszPath, pErrInfo);
1372 return VINF_SUCCESS;
1373
1374#elif defined(RT_OS_OS2)
1375 RT_NOREF4(pFsObjState1, pFsObjState2, pszPath, pErrInfo);
1376 return VINF_SUCCESS;
1377
1378#else
1379 /*
1380 * Compare the ino+dev, then the uid+gid and finally the important mode
1381 * bits. Technically the first one should be enough, but we're paranoid.
1382 */
1383 if ( pFsObjState1->Stat.st_ino != pFsObjState2->Stat.st_ino
1384 || pFsObjState1->Stat.st_dev != pFsObjState2->Stat.st_dev)
1385 return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo,
1386 "The native handle is not the same as '", pszPath, "' (ino/dev)");
1387 if ( pFsObjState1->Stat.st_uid != pFsObjState2->Stat.st_uid
1388 || pFsObjState1->Stat.st_gid != pFsObjState2->Stat.st_gid)
1389 return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo,
1390 "The native handle is not the same as '", pszPath, "' (uid/gid)");
1391 if ( (pFsObjState1->Stat.st_mode & (S_IFMT | S_IWUSR | S_IWGRP | S_IWOTH))
1392 != (pFsObjState2->Stat.st_mode & (S_IFMT | S_IWUSR | S_IWGRP | S_IWOTH)))
1393 return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo,
1394 "The native handle is not the same as '", pszPath, "' (mode)");
1395 return VINF_SUCCESS;
1396#endif
1397}
1398
1399
1400/**
1401 * Verifies a file system object (file or directory).
1402 *
1403 * @returns VBox status code, error buffer filled on failure.
1404 * @param pFsObjState The file system object information/state to be
1405 * verified.
1406 * @param fDir Whether this is a directory or a file.
1407 * @param fRelaxed Whether we can be more relaxed about this
1408 * directory (only used for grand parent
1409 * directories).
1410 * @param fSymlinksAllowed Flag whether symlinks are allowed or not.
1411 * If allowed the symlink object is verified not the target.
1412 * @param pszPath The path to the object. For error messages and
1413 * securing a couple of hacks.
1414 * @param pErrInfo The error info structure.
1415 */
1416static int supR3HardenedVerifyFsObject(PCSUPR3HARDENEDFSOBJSTATE pFsObjState, bool fDir, bool fRelaxed,
1417 bool fSymlinksAllowed, const char *pszPath, PRTERRINFO pErrInfo)
1418{
1419#if defined(RT_OS_WINDOWS)
1420 /** @todo Windows hardening. */
1421 RT_NOREF(pFsObjState, fDir, fRelaxed, fSymlinksAllowed, pszPath, pErrInfo);
1422 return VINF_SUCCESS;
1423
1424#elif defined(RT_OS_OS2)
1425 /* No hardening here - it's a single user system. */
1426 RT_NOREF(pFsObjState, fDir, fRelaxed, fSymlinksAllowed, pszPath, pErrInfo);
1427 return VINF_SUCCESS;
1428
1429#else
1430 /*
1431 * The owner must be root.
1432 *
1433 * This can be extended to include predefined system users if necessary.
1434 */
1435 if (pFsObjState->Stat.st_uid != 0)
1436 return supR3HardenedSetError3(VERR_SUPLIB_OWNER_NOT_ROOT, pErrInfo, "The owner is not root: '", pszPath, "'");
1437
1438 /*
1439 * The object type must be directory or file. It can be a symbolic link
1440 * if explicitely allowed. Otherwise this and other risky stuff is not allowed
1441 * (sorry dude, but we're paranoid on purpose here).
1442 */
1443 if ( !S_ISLNK(pFsObjState->Stat.st_mode)
1444 || !fSymlinksAllowed)
1445 {
1446
1447 if ( !S_ISDIR(pFsObjState->Stat.st_mode)
1448 && !S_ISREG(pFsObjState->Stat.st_mode))
1449 {
1450 if (S_ISLNK(pFsObjState->Stat.st_mode))
1451 return supR3HardenedSetError3(VERR_SUPLIB_SYMLINKS_ARE_NOT_PERMITTED, pErrInfo,
1452 "Symlinks are not permitted: '", pszPath, "'");
1453 return supR3HardenedSetError3(VERR_SUPLIB_NOT_DIR_NOT_FILE, pErrInfo,
1454 "Not regular file or directory: '", pszPath, "'");
1455 }
1456 if (fDir != !!S_ISDIR(pFsObjState->Stat.st_mode))
1457 {
1458 if (S_ISDIR(pFsObjState->Stat.st_mode))
1459 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
1460 "Expected file but found directory: '", pszPath, "'");
1461 return supR3HardenedSetError3(VERR_SUPLIB_IS_FILE, pErrInfo,
1462 "Expected directory but found file: '", pszPath, "'");
1463 }
1464 }
1465
1466 /*
1467 * The group does not matter if it does not have write access, if it has
1468 * write access it must be group 0 (root/wheel/whatever).
1469 *
1470 * This can be extended to include predefined system groups or groups that
1471 * only root is member of.
1472 */
1473 if ( (pFsObjState->Stat.st_mode & S_IWGRP)
1474 && pFsObjState->Stat.st_gid != 0)
1475 {
1476# ifdef RT_OS_DARWIN
1477 /* HACK ALERT: On Darwin /Applications is root:admin with admin having
1478 full access. So, to work around we relax the hardening a bit and
1479 permit grand parents and beyond to be group writable by admin. */
1480 /** @todo dynamically resolve the admin group? */
1481 bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 80 /*admin*/ || suplibHardenedStrCmp(pszPath, "/Applications");
1482
1483# elif defined(RT_OS_FREEBSD)
1484 /* HACK ALERT: PC-BSD 9 has group-writable /usr/pib directory which is
1485 similar to /Applications on OS X (see above).
1486 On FreeBSD root is normally the only member of this group, on
1487 PC-BSD the default user is a member. */
1488 /** @todo dynamically resolve the operator group? */
1489 bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 5 /*operator*/ || suplibHardenedStrCmp(pszPath, "/usr/pbi");
1490 NOREF(fRelaxed);
1491# elif defined(RT_OS_SOLARIS)
1492 /* HACK ALERT: Solaris has group-writable /usr/lib/iconv directory from
1493 which the appropriate module is loaded.
1494 By default only root and daemon are part of that group.
1495 . */
1496 /** @todo dynamically resolve the bin group? */
1497 bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 2 /*bin*/ || suplibHardenedStrCmp(pszPath, "/usr/lib/iconv");
1498# else
1499 NOREF(fRelaxed);
1500 bool fBad = true;
1501# endif
1502 if (fBad)
1503 return supR3HardenedSetError3(VERR_SUPLIB_WRITE_NON_SYS_GROUP, pErrInfo,
1504 "An unknown (and thus untrusted) group has write access to '", pszPath,
1505 "' and we therefore cannot trust the directory content or that of any subdirectory");
1506 }
1507
1508 /*
1509 * World must not have write access. There is no relaxing this rule.
1510 * Linux exception: Symbolic links are always give permission 0777, there
1511 * is no lchmod or lchown APIs. The permissions on parent
1512 * directory that contains the symbolic link is what is
1513 * decising wrt to modifying it. (Caller is expected not
1514 * to allow symbolic links in the first path component.)
1515 */
1516 if ( (pFsObjState->Stat.st_mode & S_IWOTH)
1517# ifdef RT_OS_LINUX
1518 && ( !S_ISLNK(pFsObjState->Stat.st_mode)
1519 || !fSymlinksAllowed /* paranoia */)
1520# endif
1521 )
1522 return supR3HardenedSetError3(VERR_SUPLIB_WORLD_WRITABLE, pErrInfo,
1523 "World writable: '", pszPath, "'");
1524
1525 /*
1526 * Check the ACLs.
1527 */
1528 /** @todo */
1529
1530 return VINF_SUCCESS;
1531#endif
1532}
1533
1534
1535/**
1536 * Verifies that the file system object indicated by the native handle is the
1537 * same as the one @a pFsObjState indicates.
1538 *
1539 * @returns VBox status code, error buffer filled on failure.
1540 * @param hNative The native handle to the object @a pszPath
1541 * specifies and this should be verified to be the
1542 * same file system object.
1543 * @param pFsObjState The information/state returned by a previous
1544 * query call.
1545 * @param pszPath The path to the object @a pFsObjState
1546 * describes. (For the error message.)
1547 * @param pErrInfo The error info structure.
1548 */
1549static int supR3HardenedVerifySameFsObject(RTHCUINTPTR hNative, PCSUPR3HARDENEDFSOBJSTATE pFsObjState,
1550 const char *pszPath, PRTERRINFO pErrInfo)
1551{
1552 SUPR3HARDENEDFSOBJSTATE FsObjState2;
1553 int rc = supR3HardenedQueryFsObjectByHandle(hNative, &FsObjState2, pszPath, pErrInfo);
1554 if (RT_SUCCESS(rc))
1555 rc = supR3HardenedIsSameFsObject(pFsObjState, &FsObjState2, pszPath, pErrInfo);
1556 return rc;
1557}
1558
1559
1560/**
1561 * Does the recursive directory enumeration.
1562 *
1563 * @returns VBox status code, error buffer filled on failure.
1564 * @param pszDirPath The path buffer containing the subdirectory to
1565 * enumerate followed by a slash (this is never
1566 * the root slash). The buffer is RTPATH_MAX in
1567 * size and anything starting at @a cchDirPath
1568 * - 1 and beyond is scratch space.
1569 * @param cchDirPath The length of the directory path + slash.
1570 * @param pFsObjState Pointer to the file system object state buffer.
1571 * On input this will hold the stats for
1572 * the directory @a pszDirPath indicates and will
1573 * be used to verified that we're opening the same
1574 * thing.
1575 * @param fRecursive Whether to recurse into subdirectories.
1576 * @param pErrInfo The error info structure.
1577 */
1578static int supR3HardenedVerifyDirRecursive(char *pszDirPath, size_t cchDirPath, PSUPR3HARDENEDFSOBJSTATE pFsObjState,
1579 bool fRecursive, PRTERRINFO pErrInfo)
1580{
1581#if defined(RT_OS_WINDOWS)
1582 /** @todo Windows hardening. */
1583 RT_NOREF5(pszDirPath, cchDirPath, pFsObjState, fRecursive, pErrInfo);
1584 return VINF_SUCCESS;
1585
1586#elif defined(RT_OS_OS2)
1587 /* No hardening here - it's a single user system. */
1588 RT_NOREF5(pszDirPath, cchDirPath, pFsObjState, fRecursive, pErrInfo);
1589 return VINF_SUCCESS;
1590
1591#else
1592 /*
1593 * Open the directory. Now, we could probably eliminate opendir here
1594 * and go down on kernel API level (open + getdents for instance), however
1595 * that's not very portable and hopefully not necessary.
1596 */
1597 DIR *pDir = opendir(pszDirPath);
1598 if (!pDir)
1599 {
1600 /* Ignore access errors. */
1601 if (errno == EACCES)
1602 return VINF_SUCCESS;
1603 return supR3HardenedSetErrorN(VERR_SUPLIB_DIR_ENUM_FAILED, pErrInfo,
1604 5, "opendir failed with ", strerror(errno), " on '", pszDirPath, "'");
1605 }
1606 if (dirfd(pDir) != -1)
1607 {
1608 int rc = supR3HardenedVerifySameFsObject(dirfd(pDir), pFsObjState, pszDirPath, pErrInfo);
1609 if (RT_FAILURE(rc))
1610 {
1611 closedir(pDir);
1612 return rc;
1613 }
1614 }
1615
1616 /*
1617 * Enumerate the directory, check all the requested bits.
1618 */
1619 int rc = VINF_SUCCESS;
1620 for (;;)
1621 {
1622 pszDirPath[cchDirPath] = '\0'; /* for error messages. */
1623
1624 struct dirent Entry;
1625 struct dirent *pEntry;
1626#if RT_GNUC_PREREQ(4, 6)
1627# pragma GCC diagnostic push
1628# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1629#endif
1630 int iErr = readdir_r(pDir, &Entry, &pEntry);
1631#if RT_GNUC_PREREQ(4, 6)
1632# pragma GCC diagnostic pop
1633#endif
1634 if (iErr)
1635 {
1636 rc = supR3HardenedSetErrorN(VERR_SUPLIB_DIR_ENUM_FAILED, pErrInfo,
1637 5, "readdir_r failed with ", strerror(iErr), " in '", pszDirPath, "'");
1638 break;
1639 }
1640 if (!pEntry)
1641 break;
1642
1643 /*
1644 * Check the length and copy it into the path buffer so it can be
1645 * stat()'ed.
1646 */
1647 size_t cchName = suplibHardenedStrLen(pEntry->d_name);
1648 if (cchName + cchDirPath > SUPR3HARDENED_MAX_PATH)
1649 {
1650 rc = supR3HardenedSetErrorN(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo,
1651 4, "Path grew too long during recursion: '", pszDirPath, pEntry->d_name, "'");
1652 break;
1653 }
1654 suplibHardenedMemCopy(&pszDirPath[cchName], pEntry->d_name, cchName + 1);
1655
1656 /*
1657 * Query the information about the entry and verify it.
1658 * (We don't bother skipping '.' and '..' at this point, a little bit
1659 * of extra checks doesn't hurt and neither requires relaxed handling.)
1660 */
1661 rc = supR3HardenedQueryFsObjectByPath(pszDirPath, pFsObjState, pErrInfo);
1662 if (RT_SUCCESS(rc))
1663 break;
1664 rc = supR3HardenedVerifyFsObject(pFsObjState, S_ISDIR(pFsObjState->Stat.st_mode), false /*fRelaxed*/,
1665 false /*fSymlinksAllowed*/, pszDirPath, pErrInfo);
1666 if (RT_FAILURE(rc))
1667 break;
1668
1669 /*
1670 * Recurse into subdirectories if requested.
1671 */
1672 if ( fRecursive
1673 && S_ISDIR(pFsObjState->Stat.st_mode)
1674 && suplibHardenedStrCmp(pEntry->d_name, ".")
1675 && suplibHardenedStrCmp(pEntry->d_name, ".."))
1676 {
1677 pszDirPath[cchDirPath + cchName] = RTPATH_SLASH;
1678 pszDirPath[cchDirPath + cchName + 1] = '\0';
1679
1680 rc = supR3HardenedVerifyDirRecursive(pszDirPath, cchDirPath + cchName + 1, pFsObjState,
1681 fRecursive, pErrInfo);
1682 if (RT_FAILURE(rc))
1683 break;
1684 }
1685 }
1686
1687 closedir(pDir);
1688 return VINF_SUCCESS;
1689#endif
1690}
1691
1692
1693/**
1694 * Worker for SUPR3HardenedVerifyDir.
1695 *
1696 * @returns See SUPR3HardenedVerifyDir.
1697 * @param pszDirPath See SUPR3HardenedVerifyDir.
1698 * @param fRecursive See SUPR3HardenedVerifyDir.
1699 * @param fCheckFiles See SUPR3HardenedVerifyDir.
1700 * @param pErrInfo See SUPR3HardenedVerifyDir.
1701 */
1702DECLHIDDEN(int) supR3HardenedVerifyDir(const char *pszDirPath, bool fRecursive, bool fCheckFiles, PRTERRINFO pErrInfo)
1703{
1704 /*
1705 * Validate the input path and parse it.
1706 */
1707 SUPR3HARDENEDPATHINFO Info;
1708 int rc = supR3HardenedVerifyPathSanity(pszDirPath, pErrInfo, &Info);
1709 if (RT_FAILURE(rc))
1710 return rc;
1711
1712 /*
1713 * Verify each component from the root up.
1714 */
1715 SUPR3HARDENEDFSOBJSTATE FsObjState;
1716 uint32_t const cComponents = Info.cComponents;
1717 for (uint32_t iComponent = 0; iComponent < cComponents; iComponent++)
1718 {
1719 bool fRelaxed = iComponent + 2 < cComponents;
1720 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0';
1721 rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo);
1722 if (RT_SUCCESS(rc))
1723 rc = supR3HardenedVerifyFsObject(&FsObjState, true /*fDir*/, fRelaxed,
1724 false /*fSymlinksAllowed*/, Info.szPath, pErrInfo);
1725 if (RT_FAILURE(rc))
1726 return rc;
1727 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = iComponent + 1 != cComponents ? RTPATH_SLASH : '\0';
1728 }
1729
1730 /*
1731 * Check files and subdirectories if requested.
1732 */
1733 if (fCheckFiles || fRecursive)
1734 {
1735 Info.szPath[Info.cch] = RTPATH_SLASH;
1736 Info.szPath[Info.cch + 1] = '\0';
1737 return supR3HardenedVerifyDirRecursive(Info.szPath, Info.cch + 1, &FsObjState,
1738 fRecursive, pErrInfo);
1739 }
1740
1741 return VINF_SUCCESS;
1742}
1743
1744
1745/**
1746 * Verfies a file.
1747 *
1748 * @returns VBox status code, error buffer filled on failure.
1749 * @param pszFilename The file to verify.
1750 * @param hNativeFile Handle to the file, verify that it's the same
1751 * as we ended up with when verifying the path.
1752 * RTHCUINTPTR_MAX means NIL here.
1753 * @param fMaybe3rdParty Set if the file is could be a supplied by a
1754 * third party. Different validation rules may
1755 * apply to 3rd party code on some platforms.
1756 * @param pErrInfo Where to return extended error information.
1757 * Optional.
1758 */
1759DECLHIDDEN(int) supR3HardenedVerifyFile(const char *pszFilename, RTHCUINTPTR hNativeFile,
1760 bool fMaybe3rdParty, PRTERRINFO pErrInfo)
1761{
1762 /*
1763 * Validate the input path and parse it.
1764 */
1765 SUPR3HARDENEDPATHINFO Info;
1766 int rc = supR3HardenedVerifyPathSanity(pszFilename, pErrInfo, &Info);
1767 if (RT_FAILURE(rc))
1768 return rc;
1769 if (Info.fDirSlash)
1770 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
1771 "The file path specifies a directory: '", pszFilename, "'");
1772
1773 /*
1774 * Verify each component from the root up.
1775 */
1776 SUPR3HARDENEDFSOBJSTATE FsObjState;
1777 uint32_t const cComponents = Info.cComponents;
1778 for (uint32_t iComponent = 0; iComponent < cComponents; iComponent++)
1779 {
1780 bool fFinal = iComponent + 1 == cComponents;
1781 bool fRelaxed = iComponent + 2 < cComponents;
1782 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0';
1783 rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo);
1784 if (RT_SUCCESS(rc))
1785 rc = supR3HardenedVerifyFsObject(&FsObjState, !fFinal /*fDir*/, fRelaxed,
1786 false /*fSymlinksAllowed*/, Info.szPath, pErrInfo);
1787 if (RT_FAILURE(rc))
1788 return rc;
1789 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = !fFinal ? RTPATH_SLASH : '\0';
1790 }
1791
1792 /*
1793 * Verify the file handle against the last component, if specified.
1794 */
1795 if (hNativeFile != RTHCUINTPTR_MAX)
1796 {
1797 rc = supR3HardenedVerifySameFsObject(hNativeFile, &FsObjState, Info.szPath, pErrInfo);
1798 if (RT_FAILURE(rc))
1799 return rc;
1800 }
1801
1802#ifdef RT_OS_WINDOWS
1803 /*
1804 * The files shall be signed on windows, verify that.
1805 */
1806 rc = VINF_SUCCESS;
1807 HANDLE hVerify;
1808 if (hNativeFile == RTHCUINTPTR_MAX)
1809 {
1810 PRTUTF16 pwszPath;
1811 rc = RTStrToUtf16(pszFilename, &pwszPath);
1812 if (RT_SUCCESS(rc))
1813 {
1814 hVerify = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1815 RTUtf16Free(pwszPath);
1816 }
1817 else
1818 {
1819 rc = RTErrInfoSetF(pErrInfo, rc, "Error converting '%s' to UTF-16: %Rrc", pszFilename, rc);
1820 hVerify = INVALID_HANDLE_VALUE;
1821 }
1822 }
1823 else
1824 {
1825 NTSTATUS rcNt = NtDuplicateObject(NtCurrentProcess(), (HANDLE)hNativeFile, NtCurrentProcess(), &hVerify,
1826 GENERIC_READ, 0 /*HandleAttributes*/, 0 /*Options*/);
1827 if (!NT_SUCCESS(rcNt))
1828 hVerify = INVALID_HANDLE_VALUE;
1829 }
1830 if (hVerify != INVALID_HANDLE_VALUE)
1831 {
1832# ifdef VBOX_WITH_HARDENING
1833 uint32_t fFlags = SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING;
1834 if (!fMaybe3rdParty)
1835 fFlags = SUPHNTVI_F_REQUIRE_BUILD_CERT;
1836 const char *pszSuffix = RTPathSuffix(pszFilename);
1837 if ( pszSuffix
1838 && pszSuffix[0] == '.'
1839 && ( RT_C_TO_LOWER(pszSuffix[1]) == 'r'
1840 || RT_C_TO_LOWER(pszSuffix[1]) == 'g')
1841 && RT_C_TO_LOWER(pszSuffix[2]) == 'c'
1842 && pszSuffix[3] == '\0' )
1843 fFlags |= SUPHNTVI_F_RC_IMAGE;
1844# ifndef IN_SUP_R3_STATIC /* Not in VBoxCpuReport and friends. */
1845 rc = supHardenedWinVerifyImageByHandleNoName(hVerify, fFlags, pErrInfo);
1846# endif
1847# else
1848 RT_NOREF1(fMaybe3rdParty);
1849# endif
1850 NtClose(hVerify);
1851 }
1852 else if (RT_SUCCESS(rc))
1853 rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(RtlGetLastWin32Error()),
1854 "Error %u trying to open (or duplicate handle for) '%s'", RtlGetLastWin32Error(), pszFilename);
1855 if (RT_FAILURE(rc))
1856 return rc;
1857#else
1858 RT_NOREF1(fMaybe3rdParty);
1859#endif
1860
1861 return VINF_SUCCESS;
1862}
1863
1864
1865#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX)
1866/**
1867 * Verfies a file following symlinks.
1868 *
1869 * @returns VBox status code, error buffer filled on failure.
1870 * @param pszFilename The file to verify.
1871 * @param hNativeFile Handle to the file, verify that it's the same
1872 * as we ended up with when verifying the path.
1873 * RTHCUINTPTR_MAX means NIL here.
1874 * @param fMaybe3rdParty Set if the file is could be a supplied by a
1875 * third party. Different validation rules may
1876 * apply to 3rd party code on some platforms.
1877 * @param pErrInfo Where to return extended error information.
1878 * Optional.
1879 *
1880 * @note This is only used on OS X for libraries loaded with dlopen() because
1881 * the frameworks use symbolic links to point to the relevant library.
1882 *
1883 * @sa supR3HardenedVerifyFile
1884 */
1885DECLHIDDEN(int) supR3HardenedVerifyFileFollowSymlinks(const char *pszFilename, RTHCUINTPTR hNativeFile, bool fMaybe3rdParty,
1886 PRTERRINFO pErrInfo)
1887{
1888 RT_NOREF1(fMaybe3rdParty);
1889
1890 /*
1891 * Validate the input path and parse it.
1892 */
1893 SUPR3HARDENEDPATHINFO Info;
1894 int rc = supR3HardenedVerifyPathSanity(pszFilename, pErrInfo, &Info);
1895 if (RT_FAILURE(rc))
1896 return rc;
1897 if (Info.fDirSlash)
1898 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
1899 "The file path specifies a directory: '", pszFilename, "'");
1900
1901 /*
1902 * Verify each component from the root up.
1903 */
1904#ifndef SUP_HARDENED_VERIFY_FOLLOW_SYMLINKS_USE_REALPATH
1905 uint32_t iLoops = 0;
1906#endif
1907 SUPR3HARDENEDFSOBJSTATE FsObjState;
1908 uint32_t iComponent = 0;
1909 while (iComponent < Info.cComponents)
1910 {
1911 bool fFinal = iComponent + 1 == Info.cComponents;
1912 bool fRelaxed = iComponent + 2 < Info.cComponents;
1913 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0';
1914 rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo);
1915 if (RT_SUCCESS(rc))
1916 {
1917 /*
1918 * In case the component is a symlink expand it and start from the beginning after
1919 * verifying it has the proper access rights.
1920 * Furthermore only allow symlinks which don't contain any .. or . in the target
1921 * (enforced by supR3HardenedVerifyPathSanity).
1922 */
1923 rc = supR3HardenedVerifyFsObject(&FsObjState, !fFinal /*fDir*/, fRelaxed,
1924 true /*fSymlinksAllowed*/, Info.szPath, pErrInfo);
1925 if ( RT_SUCCESS(rc)
1926 && S_ISLNK(FsObjState.Stat.st_mode))
1927 {
1928#if SUP_HARDENED_VERIFY_FOLLOW_SYMLINKS_USE_REALPATH /* Another approach using realpath() and verifying the result when encountering a symlink. */
1929 char *pszFilenameResolved = realpath(pszFilename, NULL);
1930 if (pszFilenameResolved)
1931 {
1932 rc = supR3HardenedVerifyFile(pszFilenameResolved, hNativeFile, fMaybe3rdParty, pErrInfo);
1933 free(pszFilenameResolved);
1934 return rc;
1935 }
1936 else
1937 {
1938 int iErr = errno;
1939 supR3HardenedError(VERR_ACCESS_DENIED, false /*fFatal*/,
1940 "supR3HardenedVerifyFileFollowSymlinks: Failed to resolve the real path '%s': %s (%d)\n",
1941 pszFilename, strerror(iErr), iErr);
1942 return supR3HardenedSetError4(VERR_ACCESS_DENIED, pErrInfo,
1943 "realpath failed for '", pszFilename, "': ", strerror(iErr));
1944 }
1945#else
1946 /* Don't loop forever. */
1947 iLoops++;
1948 if (iLoops < 8)
1949 {
1950 /*
1951 * Construct new path by replacing the current component by the symlink value.
1952 * Note! readlink() is a weird API that doesn't necessarily indicates if the
1953 * buffer is too small.
1954 */
1955 char szPath[RTPATH_MAX];
1956 size_t const cchBefore = Info.aoffComponents[iComponent]; /* includes slash */
1957 size_t const cchAfter = fFinal ? 0 : 1 /*slash*/ + Info.cch - Info.aoffComponents[iComponent + 1];
1958 if (sizeof(szPath) > cchBefore + cchAfter + 2)
1959 {
1960 ssize_t cchTarget = readlink(Info.szPath, szPath, sizeof(szPath) - 1);
1961 if (cchTarget > 0)
1962 {
1963 /* Some serious paranoia against embedded zero terminator and weird return values. */
1964 szPath[cchTarget] = '\0';
1965 size_t cchLink = strlen(szPath);
1966
1967 /* Strip trailing dirslashes of non-final link. */
1968 if (!fFinal)
1969 while (cchLink > 1 and szPath[cchLink - 1] == '/')
1970 cchLink--;
1971
1972 /* Check link value sanity and buffer size. */
1973 if (cchLink == 0)
1974 return supR3HardenedSetError3(VERR_ACCESS_DENIED, pErrInfo,
1975 "Bad readlink return for '", Info.szPath, "'");
1976 if (szPath[0] == '/')
1977 return supR3HardenedSetError3(VERR_ACCESS_DENIED, pErrInfo,
1978 "Absolute symbolic link not allowed: '", szPath, "'");
1979 if (cchBefore + cchLink + cchAfter + 1 /*terminator*/ > sizeof(szPath))
1980 return supR3HardenedSetError(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo,
1981 "Symlinks causing too long path!");
1982
1983 /* Construct the new path. */
1984 if (cchBefore)
1985 memmove(&szPath[cchBefore], &szPath[0], cchLink);
1986 memcpy(&szPath[0], Info.szPath, cchBefore);
1987 if (!cchAfter)
1988 szPath[cchBefore + cchLink] = '\0';
1989 else
1990 {
1991 szPath[cchBefore + cchLink] = RTPATH_SLASH;
1992 memcpy(&szPath[cchBefore + cchLink + 1],
1993 &Info.szPath[Info.aoffComponents[iComponent + 1]],
1994 cchAfter); /* cchAfter includes a zero terminator */
1995 }
1996
1997 /* Parse, copy and check the sanity (no '..' or '.') of the altered path. */
1998 rc = supR3HardenedVerifyPathSanity(szPath, pErrInfo, &Info);
1999 if (RT_FAILURE(rc))
2000 return rc;
2001 if (Info.fDirSlash)
2002 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
2003 "The file path specifies a directory: '", szPath, "'");
2004
2005 /* Restart from the current component. */
2006 continue;
2007 }
2008 int iErr = errno;
2009 supR3HardenedError(VERR_ACCESS_DENIED, false /*fFatal*/,
2010 "supR3HardenedVerifyFileFollowSymlinks: Failed to readlink '%s': %s (%d)\n",
2011 Info.szPath, strerror(iErr), iErr);
2012 return supR3HardenedSetError4(VERR_ACCESS_DENIED, pErrInfo,
2013 "readlink failed for '", Info.szPath, "': ", strerror(iErr));
2014 }
2015 return supR3HardenedSetError(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo, "Path too long for symlink replacing!");
2016 }
2017 else
2018 return supR3HardenedSetError3(VERR_TOO_MANY_SYMLINKS, pErrInfo,
2019 "Too many symbolic links: '", pszFilename, "'");
2020#endif
2021 }
2022 }
2023 if (RT_FAILURE(rc))
2024 return rc;
2025 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = !fFinal ? RTPATH_SLASH : '\0';
2026 iComponent++;
2027 }
2028
2029 /*
2030 * Verify the file handle against the last component, if specified.
2031 */
2032 if (hNativeFile != RTHCUINTPTR_MAX)
2033 {
2034 rc = supR3HardenedVerifySameFsObject(hNativeFile, &FsObjState, Info.szPath, pErrInfo);
2035 if (RT_FAILURE(rc))
2036 return rc;
2037 }
2038
2039 return VINF_SUCCESS;
2040}
2041#endif /* RT_OS_DARWIN || RT_OS_LINUX */
2042
2043
2044/**
2045 * Gets the pre-init data for the hand-over to the other version
2046 * of this code.
2047 *
2048 * The reason why we pass this information on is that it contains
2049 * open directories and files. Later it may include even more info
2050 * (int the verified arrays mostly).
2051 *
2052 * The receiver is supR3HardenedRecvPreInitData.
2053 *
2054 * @param pPreInitData Where to store it.
2055 */
2056DECLHIDDEN(void) supR3HardenedGetPreInitData(PSUPPREINITDATA pPreInitData)
2057{
2058 pPreInitData->cInstallFiles = RT_ELEMENTS(g_aSupInstallFiles);
2059 pPreInitData->paInstallFiles = &g_aSupInstallFiles[0];
2060 pPreInitData->paVerifiedFiles = &g_aSupVerifiedFiles[0];
2061
2062 pPreInitData->cVerifiedDirs = RT_ELEMENTS(g_aSupVerifiedDirs);
2063 pPreInitData->paVerifiedDirs = &g_aSupVerifiedDirs[0];
2064}
2065
2066
2067/**
2068 * Receives the pre-init data from the static executable stub.
2069 *
2070 * @returns VBox status code. Will not bitch on failure since the
2071 * runtime isn't ready for it, so that is left to the exe stub.
2072 *
2073 * @param pPreInitData The hand-over data.
2074 */
2075DECLHIDDEN(int) supR3HardenedRecvPreInitData(PCSUPPREINITDATA pPreInitData)
2076{
2077 /*
2078 * Compare the array lengths and the contents of g_aSupInstallFiles.
2079 */
2080 if ( pPreInitData->cInstallFiles != RT_ELEMENTS(g_aSupInstallFiles)
2081 || pPreInitData->cVerifiedDirs != RT_ELEMENTS(g_aSupVerifiedDirs))
2082 return VERR_VERSION_MISMATCH;
2083 SUPINSTFILE const *paInstallFiles = pPreInitData->paInstallFiles;
2084 for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++)
2085 if ( g_aSupInstallFiles[iFile].enmDir != paInstallFiles[iFile].enmDir
2086 || g_aSupInstallFiles[iFile].enmType != paInstallFiles[iFile].enmType
2087 || g_aSupInstallFiles[iFile].fOptional != paInstallFiles[iFile].fOptional
2088 || suplibHardenedStrCmp(g_aSupInstallFiles[iFile].pszFile, paInstallFiles[iFile].pszFile))
2089 return VERR_VERSION_MISMATCH;
2090
2091 /*
2092 * Check that we're not called out of order.
2093 * If dynamic linking it screwed up, we may end up here.
2094 */
2095 if ( !ASMMemIsZero(&g_aSupVerifiedFiles[0], sizeof(g_aSupVerifiedFiles))
2096 || !ASMMemIsZero(&g_aSupVerifiedDirs[0], sizeof(g_aSupVerifiedDirs)))
2097 return VERR_WRONG_ORDER;
2098
2099 /*
2100 * Copy the verification data over.
2101 */
2102 suplibHardenedMemCopy(&g_aSupVerifiedFiles[0], pPreInitData->paVerifiedFiles, sizeof(g_aSupVerifiedFiles));
2103 suplibHardenedMemCopy(&g_aSupVerifiedDirs[0], pPreInitData->paVerifiedDirs, sizeof(g_aSupVerifiedDirs));
2104 return VINF_SUCCESS;
2105}
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