VirtualBox

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

Last change on this file since 68529 was 68344, checked in by vboxsync, 7 years ago

SUPR3HardendeVerify.cpp: Skip double root slashes on UNIXy systems. Needed for darwin, see bugref:8952.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use