VirtualBox

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

Last change on this file since 67981 was 66842, checked in by vboxsync, 7 years ago

Support/Hardening: Use realpath() to get absolute paths upon encountering the first symlinks

  • 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 66842 2017-05-08 21:03: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 /*
1194 * The root slash should be alone to avoid UNC confusion.
1195 */
1196 if (RTPATH_IS_SLASH(pszSrc[0]))
1197 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_CLEAN, pErrInfo,
1198 "The path is not clean of leading double slashes: '", pszPath, "'");
1199 /*
1200 * Check each component. No parent references.
1201 */
1202 pInfo->cComponents = 0;
1203 pInfo->fDirSlash = false;
1204 while (pszSrc[0])
1205 {
1206 /* Sanity checks. */
1207 if ( pszSrc[0] == '.'
1208 && pszSrc[1] == '.'
1209 && RTPATH_IS_SLASH(pszSrc[2]))
1210 return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo,
1211 "The path is not absolute: '", pszPath, "'");
1212
1213 /* Record the start of the component. */
1214 if (pInfo->cComponents >= RT_ELEMENTS(pInfo->aoffComponents) - 1)
1215 return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_MANY_COMPONENTS, pErrInfo,
1216 "The path has too many components: '", pszPath, "'");
1217 pInfo->aoffComponents[pInfo->cComponents++] = pszDst - &pInfo->szPath[0];
1218
1219 /* Traverse to the end of the component, copying it as we go along. */
1220 while (pszSrc[0])
1221 {
1222 if (RTPATH_IS_SLASH(pszSrc[0]))
1223 {
1224 pszSrc++;
1225 if (*pszSrc)
1226 *pszDst++ = RTPATH_SLASH;
1227 else
1228 pInfo->fDirSlash = true;
1229 break;
1230 }
1231 *pszDst++ = *pszSrc++;
1232 if ((uintptr_t)(pszDst - &pInfo->szPath[0]) >= SUPR3HARDENED_MAX_PATH)
1233 return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo,
1234 "The path is too long: '", pszPath, "'");
1235 }
1236
1237 /* Skip double slashes. */
1238 while (RTPATH_IS_SLASH(*pszSrc))
1239 pszSrc++;
1240 }
1241
1242 /* Terminate the string and enter its length. */
1243 pszDst[0] = '\0';
1244 pszDst[1] = '\0'; /* for aoffComponents */
1245 pInfo->cch = (uint16_t)(pszDst - &pInfo->szPath[0]);
1246 pInfo->aoffComponents[pInfo->cComponents] = pInfo->cch + 1;
1247
1248 return VINF_SUCCESS;
1249}
1250
1251
1252/**
1253 * The state information collected by supR3HardenedVerifyFsObject.
1254 *
1255 * This can be used to verify that a directory we've opened for enumeration is
1256 * the same as the one that supR3HardenedVerifyFsObject just verified. It can
1257 * equally be used to verify a native specfied by the user.
1258 */
1259typedef struct SUPR3HARDENEDFSOBJSTATE
1260{
1261#ifdef RT_OS_WINDOWS
1262 /** Not implemented for windows yet. */
1263 char chTodo;
1264#else
1265 /** The stat output. */
1266 struct stat Stat;
1267#endif
1268} SUPR3HARDENEDFSOBJSTATE;
1269/** Pointer to a file system object state. */
1270typedef SUPR3HARDENEDFSOBJSTATE *PSUPR3HARDENEDFSOBJSTATE;
1271/** Pointer to a const file system object state. */
1272typedef SUPR3HARDENEDFSOBJSTATE const *PCSUPR3HARDENEDFSOBJSTATE;
1273
1274
1275/**
1276 * Query information about a file system object by path.
1277 *
1278 * @returns VBox status code, error buffer filled on failure.
1279 * @param pszPath The path to the object.
1280 * @param pFsObjState Where to return the state information.
1281 * @param pErrInfo The error info structure.
1282 */
1283static int supR3HardenedQueryFsObjectByPath(char const *pszPath, PSUPR3HARDENEDFSOBJSTATE pFsObjState, PRTERRINFO pErrInfo)
1284{
1285#if defined(RT_OS_WINDOWS)
1286 /** @todo Windows hardening. */
1287 pFsObjState->chTodo = 0;
1288 RT_NOREF2(pszPath, pErrInfo);
1289 return VINF_SUCCESS;
1290
1291#else
1292 /*
1293 * Stat the object, do not follow links.
1294 */
1295 if (lstat(pszPath, &pFsObjState->Stat) != 0)
1296 {
1297 /* Ignore access errors */
1298 if (errno != EACCES)
1299 return supR3HardenedSetErrorN(VERR_SUPLIB_STAT_FAILED, pErrInfo,
1300 5, "stat failed with ", strerror(errno), " on: '", pszPath, "'");
1301 }
1302
1303 /*
1304 * Read ACLs.
1305 */
1306 /** @todo */
1307
1308 return VINF_SUCCESS;
1309#endif
1310}
1311
1312
1313/**
1314 * Query information about a file system object by native handle.
1315 *
1316 * @returns VBox status code, error buffer filled on failure.
1317 * @param hNative The native handle to the object @a pszPath
1318 * specifies and this should be verified to be the
1319 * same file system object.
1320 * @param pFsObjState Where to return the state information.
1321 * @param pszPath The path to the object. (For the error message
1322 * only.)
1323 * @param pErrInfo The error info structure.
1324 */
1325static int supR3HardenedQueryFsObjectByHandle(RTHCUINTPTR hNative, PSUPR3HARDENEDFSOBJSTATE pFsObjState,
1326 char const *pszPath, PRTERRINFO pErrInfo)
1327{
1328#if defined(RT_OS_WINDOWS)
1329 /** @todo Windows hardening. */
1330 pFsObjState->chTodo = 0;
1331 RT_NOREF3(hNative, pszPath, pErrInfo);
1332 return VINF_SUCCESS;
1333
1334#else
1335 /*
1336 * Stat the object, do not follow links.
1337 */
1338 if (fstat((int)hNative, &pFsObjState->Stat) != 0)
1339 return supR3HardenedSetErrorN(VERR_SUPLIB_STAT_FAILED, pErrInfo,
1340 5, "fstat failed with ", strerror(errno), " on '", pszPath, "'");
1341
1342 /*
1343 * Read ACLs.
1344 */
1345 /** @todo */
1346
1347 return VINF_SUCCESS;
1348#endif
1349}
1350
1351
1352/**
1353 * Verifies that the file system object indicated by the native handle is the
1354 * same as the one @a pFsObjState indicates.
1355 *
1356 * @returns VBox status code, error buffer filled on failure.
1357 * @param pFsObjState1 File system object information/state by path.
1358 * @param pFsObjState2 File system object information/state by handle.
1359 * @param pszPath The path to the object @a pFsObjState
1360 * describes. (For the error message.)
1361 * @param pErrInfo The error info structure.
1362 */
1363static int supR3HardenedIsSameFsObject(PCSUPR3HARDENEDFSOBJSTATE pFsObjState1, PCSUPR3HARDENEDFSOBJSTATE pFsObjState2,
1364 const char *pszPath, PRTERRINFO pErrInfo)
1365{
1366#if defined(RT_OS_WINDOWS)
1367 /** @todo Windows hardening. */
1368 RT_NOREF4(pFsObjState1, pFsObjState2, pszPath, pErrInfo);
1369 return VINF_SUCCESS;
1370
1371#elif defined(RT_OS_OS2)
1372 RT_NOREF4(pFsObjState1, pFsObjState2, pszPath, pErrInfo);
1373 return VINF_SUCCESS;
1374
1375#else
1376 /*
1377 * Compare the ino+dev, then the uid+gid and finally the important mode
1378 * bits. Technically the first one should be enough, but we're paranoid.
1379 */
1380 if ( pFsObjState1->Stat.st_ino != pFsObjState2->Stat.st_ino
1381 || pFsObjState1->Stat.st_dev != pFsObjState2->Stat.st_dev)
1382 return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo,
1383 "The native handle is not the same as '", pszPath, "' (ino/dev)");
1384 if ( pFsObjState1->Stat.st_uid != pFsObjState2->Stat.st_uid
1385 || pFsObjState1->Stat.st_gid != pFsObjState2->Stat.st_gid)
1386 return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo,
1387 "The native handle is not the same as '", pszPath, "' (uid/gid)");
1388 if ( (pFsObjState1->Stat.st_mode & (S_IFMT | S_IWUSR | S_IWGRP | S_IWOTH))
1389 != (pFsObjState2->Stat.st_mode & (S_IFMT | S_IWUSR | S_IWGRP | S_IWOTH)))
1390 return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo,
1391 "The native handle is not the same as '", pszPath, "' (mode)");
1392 return VINF_SUCCESS;
1393#endif
1394}
1395
1396
1397/**
1398 * Verifies a file system object (file or directory).
1399 *
1400 * @returns VBox status code, error buffer filled on failure.
1401 * @param pFsObjState The file system object information/state to be
1402 * verified.
1403 * @param fDir Whether this is a directory or a file.
1404 * @param fRelaxed Whether we can be more relaxed about this
1405 * directory (only used for grand parent
1406 * directories).
1407 * @param fSymlinksAllowed Flag whether symlinks are allowed or not.
1408 * If allowed the symlink object is verified not the target.
1409 * @param pszPath The path to the object. For error messages and
1410 * securing a couple of hacks.
1411 * @param pErrInfo The error info structure.
1412 */
1413static int supR3HardenedVerifyFsObject(PCSUPR3HARDENEDFSOBJSTATE pFsObjState, bool fDir, bool fRelaxed,
1414 bool fSymlinksAllowed, const char *pszPath, PRTERRINFO pErrInfo)
1415{
1416#if defined(RT_OS_WINDOWS)
1417 /** @todo Windows hardening. */
1418 RT_NOREF(pFsObjState, fDir, fRelaxed, fSymlinksAllowed, pszPath, pErrInfo);
1419 return VINF_SUCCESS;
1420
1421#elif defined(RT_OS_OS2)
1422 /* No hardening here - it's a single user system. */
1423 RT_NOREF(pFsObjState, fDir, fRelaxed, fSymlinksAllowed, pszPath, pErrInfo);
1424 return VINF_SUCCESS;
1425
1426#else
1427 /*
1428 * The owner must be root.
1429 *
1430 * This can be extended to include predefined system users if necessary.
1431 */
1432 if (pFsObjState->Stat.st_uid != 0)
1433 return supR3HardenedSetError3(VERR_SUPLIB_OWNER_NOT_ROOT, pErrInfo, "The owner is not root: '", pszPath, "'");
1434
1435 /*
1436 * The object type must be directory or file. It can be a symbolic link
1437 * if explicitely allowed. Otherwise this and other risky stuff is not allowed
1438 * (sorry dude, but we're paranoid on purpose here).
1439 */
1440 if ( !S_ISLNK(pFsObjState->Stat.st_mode)
1441 || !fSymlinksAllowed)
1442 {
1443
1444 if ( !S_ISDIR(pFsObjState->Stat.st_mode)
1445 && !S_ISREG(pFsObjState->Stat.st_mode))
1446 {
1447 if (S_ISLNK(pFsObjState->Stat.st_mode))
1448 return supR3HardenedSetError3(VERR_SUPLIB_SYMLINKS_ARE_NOT_PERMITTED, pErrInfo,
1449 "Symlinks are not permitted: '", pszPath, "'");
1450 return supR3HardenedSetError3(VERR_SUPLIB_NOT_DIR_NOT_FILE, pErrInfo,
1451 "Not regular file or directory: '", pszPath, "'");
1452 }
1453 if (fDir != !!S_ISDIR(pFsObjState->Stat.st_mode))
1454 {
1455 if (S_ISDIR(pFsObjState->Stat.st_mode))
1456 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
1457 "Expected file but found directory: '", pszPath, "'");
1458 return supR3HardenedSetError3(VERR_SUPLIB_IS_FILE, pErrInfo,
1459 "Expected directory but found file: '", pszPath, "'");
1460 }
1461 }
1462
1463 /*
1464 * The group does not matter if it does not have write access, if it has
1465 * write access it must be group 0 (root/wheel/whatever).
1466 *
1467 * This can be extended to include predefined system groups or groups that
1468 * only root is member of.
1469 */
1470 if ( (pFsObjState->Stat.st_mode & S_IWGRP)
1471 && pFsObjState->Stat.st_gid != 0)
1472 {
1473# ifdef RT_OS_DARWIN
1474 /* HACK ALERT: On Darwin /Applications is root:admin with admin having
1475 full access. So, to work around we relax the hardening a bit and
1476 permit grand parents and beyond to be group writable by admin. */
1477 /** @todo dynamically resolve the admin group? */
1478 bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 80 /*admin*/ || suplibHardenedStrCmp(pszPath, "/Applications");
1479
1480# elif defined(RT_OS_FREEBSD)
1481 /* HACK ALERT: PC-BSD 9 has group-writable /usr/pib directory which is
1482 similar to /Applications on OS X (see above).
1483 On FreeBSD root is normally the only member of this group, on
1484 PC-BSD the default user is a member. */
1485 /** @todo dynamically resolve the operator group? */
1486 bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 5 /*operator*/ || suplibHardenedStrCmp(pszPath, "/usr/pbi");
1487 NOREF(fRelaxed);
1488# elif defined(RT_OS_SOLARIS)
1489 /* HACK ALERT: Solaris has group-writable /usr/lib/iconv directory from
1490 which the appropriate module is loaded.
1491 By default only root and daemon are part of that group.
1492 . */
1493 /** @todo dynamically resolve the bin group? */
1494 bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 2 /*bin*/ || suplibHardenedStrCmp(pszPath, "/usr/lib/iconv");
1495# else
1496 NOREF(fRelaxed);
1497 bool fBad = true;
1498# endif
1499 if (fBad)
1500 return supR3HardenedSetError3(VERR_SUPLIB_WRITE_NON_SYS_GROUP, pErrInfo,
1501 "An unknown (and thus untrusted) group has write access to '", pszPath,
1502 "' and we therefore cannot trust the directory content or that of any subdirectory");
1503 }
1504
1505 /*
1506 * World must not have write access. There is no relaxing this rule.
1507 * Linux exception: Symbolic links are always give permission 0777, there
1508 * is no lchmod or lchown APIs. The permissions on parent
1509 * directory that contains the symbolic link is what is
1510 * decising wrt to modifying it. (Caller is expected not
1511 * to allow symbolic links in the first path component.)
1512 */
1513 if ( (pFsObjState->Stat.st_mode & S_IWOTH)
1514# ifdef RT_OS_LINUX
1515 && ( !S_ISLNK(pFsObjState->Stat.st_mode)
1516 || !fSymlinksAllowed /* paranoia */)
1517# endif
1518 )
1519 return supR3HardenedSetError3(VERR_SUPLIB_WORLD_WRITABLE, pErrInfo,
1520 "World writable: '", pszPath, "'");
1521
1522 /*
1523 * Check the ACLs.
1524 */
1525 /** @todo */
1526
1527 return VINF_SUCCESS;
1528#endif
1529}
1530
1531
1532/**
1533 * Verifies that the file system object indicated by the native handle is the
1534 * same as the one @a pFsObjState indicates.
1535 *
1536 * @returns VBox status code, error buffer filled on failure.
1537 * @param hNative The native handle to the object @a pszPath
1538 * specifies and this should be verified to be the
1539 * same file system object.
1540 * @param pFsObjState The information/state returned by a previous
1541 * query call.
1542 * @param pszPath The path to the object @a pFsObjState
1543 * describes. (For the error message.)
1544 * @param pErrInfo The error info structure.
1545 */
1546static int supR3HardenedVerifySameFsObject(RTHCUINTPTR hNative, PCSUPR3HARDENEDFSOBJSTATE pFsObjState,
1547 const char *pszPath, PRTERRINFO pErrInfo)
1548{
1549 SUPR3HARDENEDFSOBJSTATE FsObjState2;
1550 int rc = supR3HardenedQueryFsObjectByHandle(hNative, &FsObjState2, pszPath, pErrInfo);
1551 if (RT_SUCCESS(rc))
1552 rc = supR3HardenedIsSameFsObject(pFsObjState, &FsObjState2, pszPath, pErrInfo);
1553 return rc;
1554}
1555
1556
1557/**
1558 * Does the recursive directory enumeration.
1559 *
1560 * @returns VBox status code, error buffer filled on failure.
1561 * @param pszDirPath The path buffer containing the subdirectory to
1562 * enumerate followed by a slash (this is never
1563 * the root slash). The buffer is RTPATH_MAX in
1564 * size and anything starting at @a cchDirPath
1565 * - 1 and beyond is scratch space.
1566 * @param cchDirPath The length of the directory path + slash.
1567 * @param pFsObjState Pointer to the file system object state buffer.
1568 * On input this will hold the stats for
1569 * the directory @a pszDirPath indicates and will
1570 * be used to verified that we're opening the same
1571 * thing.
1572 * @param fRecursive Whether to recurse into subdirectories.
1573 * @param pErrInfo The error info structure.
1574 */
1575static int supR3HardenedVerifyDirRecursive(char *pszDirPath, size_t cchDirPath, PSUPR3HARDENEDFSOBJSTATE pFsObjState,
1576 bool fRecursive, PRTERRINFO pErrInfo)
1577{
1578#if defined(RT_OS_WINDOWS)
1579 /** @todo Windows hardening. */
1580 RT_NOREF5(pszDirPath, cchDirPath, pFsObjState, fRecursive, pErrInfo);
1581 return VINF_SUCCESS;
1582
1583#elif defined(RT_OS_OS2)
1584 /* No hardening here - it's a single user system. */
1585 RT_NOREF5(pszDirPath, cchDirPath, pFsObjState, fRecursive, pErrInfo);
1586 return VINF_SUCCESS;
1587
1588#else
1589 /*
1590 * Open the directory. Now, we could probably eliminate opendir here
1591 * and go down on kernel API level (open + getdents for instance), however
1592 * that's not very portable and hopefully not necessary.
1593 */
1594 DIR *pDir = opendir(pszDirPath);
1595 if (!pDir)
1596 {
1597 /* Ignore access errors. */
1598 if (errno == EACCES)
1599 return VINF_SUCCESS;
1600 return supR3HardenedSetErrorN(VERR_SUPLIB_DIR_ENUM_FAILED, pErrInfo,
1601 5, "opendir failed with ", strerror(errno), " on '", pszDirPath, "'");
1602 }
1603 if (dirfd(pDir) != -1)
1604 {
1605 int rc = supR3HardenedVerifySameFsObject(dirfd(pDir), pFsObjState, pszDirPath, pErrInfo);
1606 if (RT_FAILURE(rc))
1607 {
1608 closedir(pDir);
1609 return rc;
1610 }
1611 }
1612
1613 /*
1614 * Enumerate the directory, check all the requested bits.
1615 */
1616 int rc = VINF_SUCCESS;
1617 for (;;)
1618 {
1619 pszDirPath[cchDirPath] = '\0'; /* for error messages. */
1620
1621 struct dirent Entry;
1622 struct dirent *pEntry;
1623#if RT_GNUC_PREREQ(4, 6)
1624# pragma GCC diagnostic push
1625# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1626#endif
1627 int iErr = readdir_r(pDir, &Entry, &pEntry);
1628#if RT_GNUC_PREREQ(4, 6)
1629# pragma GCC diagnostic pop
1630#endif
1631 if (iErr)
1632 {
1633 rc = supR3HardenedSetErrorN(VERR_SUPLIB_DIR_ENUM_FAILED, pErrInfo,
1634 5, "readdir_r failed with ", strerror(iErr), " in '", pszDirPath, "'");
1635 break;
1636 }
1637 if (!pEntry)
1638 break;
1639
1640 /*
1641 * Check the length and copy it into the path buffer so it can be
1642 * stat()'ed.
1643 */
1644 size_t cchName = suplibHardenedStrLen(pEntry->d_name);
1645 if (cchName + cchDirPath > SUPR3HARDENED_MAX_PATH)
1646 {
1647 rc = supR3HardenedSetErrorN(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo,
1648 4, "Path grew too long during recursion: '", pszDirPath, pEntry->d_name, "'");
1649 break;
1650 }
1651 suplibHardenedMemCopy(&pszDirPath[cchName], pEntry->d_name, cchName + 1);
1652
1653 /*
1654 * Query the information about the entry and verify it.
1655 * (We don't bother skipping '.' and '..' at this point, a little bit
1656 * of extra checks doesn't hurt and neither requires relaxed handling.)
1657 */
1658 rc = supR3HardenedQueryFsObjectByPath(pszDirPath, pFsObjState, pErrInfo);
1659 if (RT_SUCCESS(rc))
1660 break;
1661 rc = supR3HardenedVerifyFsObject(pFsObjState, S_ISDIR(pFsObjState->Stat.st_mode), false /*fRelaxed*/,
1662 false /*fSymlinksAllowed*/, pszDirPath, pErrInfo);
1663 if (RT_FAILURE(rc))
1664 break;
1665
1666 /*
1667 * Recurse into subdirectories if requested.
1668 */
1669 if ( fRecursive
1670 && S_ISDIR(pFsObjState->Stat.st_mode)
1671 && suplibHardenedStrCmp(pEntry->d_name, ".")
1672 && suplibHardenedStrCmp(pEntry->d_name, ".."))
1673 {
1674 pszDirPath[cchDirPath + cchName] = RTPATH_SLASH;
1675 pszDirPath[cchDirPath + cchName + 1] = '\0';
1676
1677 rc = supR3HardenedVerifyDirRecursive(pszDirPath, cchDirPath + cchName + 1, pFsObjState,
1678 fRecursive, pErrInfo);
1679 if (RT_FAILURE(rc))
1680 break;
1681 }
1682 }
1683
1684 closedir(pDir);
1685 return VINF_SUCCESS;
1686#endif
1687}
1688
1689
1690/**
1691 * Worker for SUPR3HardenedVerifyDir.
1692 *
1693 * @returns See SUPR3HardenedVerifyDir.
1694 * @param pszDirPath See SUPR3HardenedVerifyDir.
1695 * @param fRecursive See SUPR3HardenedVerifyDir.
1696 * @param fCheckFiles See SUPR3HardenedVerifyDir.
1697 * @param pErrInfo See SUPR3HardenedVerifyDir.
1698 */
1699DECLHIDDEN(int) supR3HardenedVerifyDir(const char *pszDirPath, bool fRecursive, bool fCheckFiles, PRTERRINFO pErrInfo)
1700{
1701 /*
1702 * Validate the input path and parse it.
1703 */
1704 SUPR3HARDENEDPATHINFO Info;
1705 int rc = supR3HardenedVerifyPathSanity(pszDirPath, pErrInfo, &Info);
1706 if (RT_FAILURE(rc))
1707 return rc;
1708
1709 /*
1710 * Verify each component from the root up.
1711 */
1712 SUPR3HARDENEDFSOBJSTATE FsObjState;
1713 uint32_t const cComponents = Info.cComponents;
1714 for (uint32_t iComponent = 0; iComponent < cComponents; iComponent++)
1715 {
1716 bool fRelaxed = iComponent + 2 < cComponents;
1717 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0';
1718 rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo);
1719 if (RT_SUCCESS(rc))
1720 rc = supR3HardenedVerifyFsObject(&FsObjState, true /*fDir*/, fRelaxed,
1721 false /*fSymlinksAllowed*/, Info.szPath, pErrInfo);
1722 if (RT_FAILURE(rc))
1723 return rc;
1724 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = iComponent + 1 != cComponents ? RTPATH_SLASH : '\0';
1725 }
1726
1727 /*
1728 * Check files and subdirectories if requested.
1729 */
1730 if (fCheckFiles || fRecursive)
1731 {
1732 Info.szPath[Info.cch] = RTPATH_SLASH;
1733 Info.szPath[Info.cch + 1] = '\0';
1734 return supR3HardenedVerifyDirRecursive(Info.szPath, Info.cch + 1, &FsObjState,
1735 fRecursive, pErrInfo);
1736 }
1737
1738 return VINF_SUCCESS;
1739}
1740
1741
1742/**
1743 * Verfies a file.
1744 *
1745 * @returns VBox status code, error buffer filled on failure.
1746 * @param pszFilename The file to verify.
1747 * @param hNativeFile Handle to the file, verify that it's the same
1748 * as we ended up with when verifying the path.
1749 * RTHCUINTPTR_MAX means NIL here.
1750 * @param fMaybe3rdParty Set if the file is could be a supplied by a
1751 * third party. Different validation rules may
1752 * apply to 3rd party code on some platforms.
1753 * @param pErrInfo Where to return extended error information.
1754 * Optional.
1755 */
1756DECLHIDDEN(int) supR3HardenedVerifyFile(const char *pszFilename, RTHCUINTPTR hNativeFile,
1757 bool fMaybe3rdParty, PRTERRINFO pErrInfo)
1758{
1759 /*
1760 * Validate the input path and parse it.
1761 */
1762 SUPR3HARDENEDPATHINFO Info;
1763 int rc = supR3HardenedVerifyPathSanity(pszFilename, pErrInfo, &Info);
1764 if (RT_FAILURE(rc))
1765 return rc;
1766 if (Info.fDirSlash)
1767 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
1768 "The file path specifies a directory: '", pszFilename, "'");
1769
1770 /*
1771 * Verify each component from the root up.
1772 */
1773 SUPR3HARDENEDFSOBJSTATE FsObjState;
1774 uint32_t const cComponents = Info.cComponents;
1775 for (uint32_t iComponent = 0; iComponent < cComponents; iComponent++)
1776 {
1777 bool fFinal = iComponent + 1 == cComponents;
1778 bool fRelaxed = iComponent + 2 < cComponents;
1779 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0';
1780 rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo);
1781 if (RT_SUCCESS(rc))
1782 rc = supR3HardenedVerifyFsObject(&FsObjState, !fFinal /*fDir*/, fRelaxed,
1783 false /*fSymlinksAllowed*/, Info.szPath, pErrInfo);
1784 if (RT_FAILURE(rc))
1785 return rc;
1786 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = !fFinal ? RTPATH_SLASH : '\0';
1787 }
1788
1789 /*
1790 * Verify the file handle against the last component, if specified.
1791 */
1792 if (hNativeFile != RTHCUINTPTR_MAX)
1793 {
1794 rc = supR3HardenedVerifySameFsObject(hNativeFile, &FsObjState, Info.szPath, pErrInfo);
1795 if (RT_FAILURE(rc))
1796 return rc;
1797 }
1798
1799#ifdef RT_OS_WINDOWS
1800 /*
1801 * The files shall be signed on windows, verify that.
1802 */
1803 rc = VINF_SUCCESS;
1804 HANDLE hVerify;
1805 if (hNativeFile == RTHCUINTPTR_MAX)
1806 {
1807 PRTUTF16 pwszPath;
1808 rc = RTStrToUtf16(pszFilename, &pwszPath);
1809 if (RT_SUCCESS(rc))
1810 {
1811 hVerify = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1812 RTUtf16Free(pwszPath);
1813 }
1814 else
1815 {
1816 rc = RTErrInfoSetF(pErrInfo, rc, "Error converting '%s' to UTF-16: %Rrc", pszFilename, rc);
1817 hVerify = INVALID_HANDLE_VALUE;
1818 }
1819 }
1820 else
1821 {
1822 NTSTATUS rcNt = NtDuplicateObject(NtCurrentProcess(), (HANDLE)hNativeFile, NtCurrentProcess(), &hVerify,
1823 GENERIC_READ, 0 /*HandleAttributes*/, 0 /*Options*/);
1824 if (!NT_SUCCESS(rcNt))
1825 hVerify = INVALID_HANDLE_VALUE;
1826 }
1827 if (hVerify != INVALID_HANDLE_VALUE)
1828 {
1829# ifdef VBOX_WITH_HARDENING
1830 uint32_t fFlags = SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING;
1831 if (!fMaybe3rdParty)
1832 fFlags = SUPHNTVI_F_REQUIRE_BUILD_CERT;
1833 const char *pszSuffix = RTPathSuffix(pszFilename);
1834 if ( pszSuffix
1835 && pszSuffix[0] == '.'
1836 && ( RT_C_TO_LOWER(pszSuffix[1]) == 'r'
1837 || RT_C_TO_LOWER(pszSuffix[1]) == 'g')
1838 && RT_C_TO_LOWER(pszSuffix[2]) == 'c'
1839 && pszSuffix[3] == '\0' )
1840 fFlags |= SUPHNTVI_F_RC_IMAGE;
1841# ifndef IN_SUP_R3_STATIC /* Not in VBoxCpuReport and friends. */
1842 rc = supHardenedWinVerifyImageByHandleNoName(hVerify, fFlags, pErrInfo);
1843# endif
1844# else
1845 RT_NOREF1(fMaybe3rdParty);
1846# endif
1847 NtClose(hVerify);
1848 }
1849 else if (RT_SUCCESS(rc))
1850 rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(RtlGetLastWin32Error()),
1851 "Error %u trying to open (or duplicate handle for) '%s'", RtlGetLastWin32Error(), pszFilename);
1852 if (RT_FAILURE(rc))
1853 return rc;
1854#else
1855 RT_NOREF1(fMaybe3rdParty);
1856#endif
1857
1858 return VINF_SUCCESS;
1859}
1860
1861
1862#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX)
1863/**
1864 * Verfies a file following symlinks.
1865 *
1866 * @returns VBox status code, error buffer filled on failure.
1867 * @param pszFilename The file to verify.
1868 * @param hNativeFile Handle to the file, verify that it's the same
1869 * as we ended up with when verifying the path.
1870 * RTHCUINTPTR_MAX means NIL here.
1871 * @param fMaybe3rdParty Set if the file is could be a supplied by a
1872 * third party. Different validation rules may
1873 * apply to 3rd party code on some platforms.
1874 * @param pErrInfo Where to return extended error information.
1875 * Optional.
1876 *
1877 * @note This is only used on OS X for libraries loaded with dlopen() because
1878 * the frameworks use symbolic links to point to the relevant library.
1879 *
1880 * @sa supR3HardenedVerifyFile
1881 */
1882DECLHIDDEN(int) supR3HardenedVerifyFileFollowSymlinks(const char *pszFilename, RTHCUINTPTR hNativeFile, bool fMaybe3rdParty,
1883 PRTERRINFO pErrInfo)
1884{
1885 RT_NOREF1(fMaybe3rdParty);
1886
1887 /*
1888 * Validate the input path and parse it.
1889 */
1890 SUPR3HARDENEDPATHINFO Info;
1891 int rc = supR3HardenedVerifyPathSanity(pszFilename, pErrInfo, &Info);
1892 if (RT_FAILURE(rc))
1893 return rc;
1894 if (Info.fDirSlash)
1895 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
1896 "The file path specifies a directory: '", pszFilename, "'");
1897
1898 /*
1899 * Verify each component from the root up.
1900 */
1901#ifndef SUP_HARDENED_VERIFY_FOLLOW_SYMLINKS_USE_REALPATH
1902 uint32_t iLoops = 0;
1903#endif
1904 SUPR3HARDENEDFSOBJSTATE FsObjState;
1905 uint32_t iComponent = 0;
1906 while (iComponent < Info.cComponents)
1907 {
1908 bool fFinal = iComponent + 1 == Info.cComponents;
1909 bool fRelaxed = iComponent + 2 < Info.cComponents;
1910 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0';
1911 rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo);
1912 if (RT_SUCCESS(rc))
1913 {
1914 /*
1915 * In case the component is a symlink expand it and start from the beginning after
1916 * verifying it has the proper access rights.
1917 * Furthermore only allow symlinks which don't contain any .. or . in the target
1918 * (enforced by supR3HardenedVerifyPathSanity).
1919 */
1920 rc = supR3HardenedVerifyFsObject(&FsObjState, !fFinal /*fDir*/, fRelaxed,
1921 true /*fSymlinksAllowed*/, Info.szPath, pErrInfo);
1922 if ( RT_SUCCESS(rc)
1923 && S_ISLNK(FsObjState.Stat.st_mode))
1924 {
1925#if SUP_HARDENED_VERIFY_FOLLOW_SYMLINKS_USE_REALPATH /* Another approach using realpath() and verifying the result when encountering a symlink. */
1926 char *pszFilenameResolved = realpath(pszFilename, NULL);
1927 if (pszFilenameResolved)
1928 {
1929 rc = supR3HardenedVerifyFile(pszFilenameResolved, hNativeFile, fMaybe3rdParty, pErrInfo);
1930 free(pszFilenameResolved);
1931 return rc;
1932 }
1933 else
1934 {
1935 int iErr = errno;
1936 supR3HardenedError(VERR_ACCESS_DENIED, false /*fFatal*/,
1937 "supR3HardenedVerifyFileFollowSymlinks: Failed to resolve the real path '%s': %s (%d)\n",
1938 pszFilename, strerror(iErr), iErr);
1939 return supR3HardenedSetError4(VERR_ACCESS_DENIED, pErrInfo,
1940 "realpath failed for '", pszFilename, "': ", strerror(iErr));
1941 }
1942#else
1943 /* Don't loop forever. */
1944 iLoops++;
1945 if (iLoops < 8)
1946 {
1947 /*
1948 * Construct new path by replacing the current component by the symlink value.
1949 * Note! readlink() is a weird API that doesn't necessarily indicates if the
1950 * buffer is too small.
1951 */
1952 char szPath[RTPATH_MAX];
1953 size_t const cchBefore = Info.aoffComponents[iComponent]; /* includes slash */
1954 size_t const cchAfter = fFinal ? 0 : 1 /*slash*/ + Info.cch - Info.aoffComponents[iComponent + 1];
1955 if (sizeof(szPath) > cchBefore + cchAfter + 2)
1956 {
1957 ssize_t cchTarget = readlink(Info.szPath, szPath, sizeof(szPath) - 1);
1958 if (cchTarget > 0)
1959 {
1960 /* Some serious paranoia against embedded zero terminator and weird return values. */
1961 szPath[cchTarget] = '\0';
1962 size_t cchLink = strlen(szPath);
1963
1964 /* Strip trailing dirslashes of non-final link. */
1965 if (!fFinal)
1966 while (cchLink > 1 and szPath[cchLink - 1] == '/')
1967 cchLink--;
1968
1969 /* Check link value sanity and buffer size. */
1970 if (cchLink == 0)
1971 return supR3HardenedSetError3(VERR_ACCESS_DENIED, pErrInfo,
1972 "Bad readlink return for '", Info.szPath, "'");
1973 if (szPath[0] == '/')
1974 return supR3HardenedSetError3(VERR_ACCESS_DENIED, pErrInfo,
1975 "Absolute symbolic link not allowed: '", szPath, "'");
1976 if (cchBefore + cchLink + cchAfter + 1 /*terminator*/ > sizeof(szPath))
1977 return supR3HardenedSetError(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo,
1978 "Symlinks causing too long path!");
1979
1980 /* Construct the new path. */
1981 if (cchBefore)
1982 memmove(&szPath[cchBefore], &szPath[0], cchLink);
1983 memcpy(&szPath[0], Info.szPath, cchBefore);
1984 if (!cchAfter)
1985 szPath[cchBefore + cchLink] = '\0';
1986 else
1987 {
1988 szPath[cchBefore + cchLink] = RTPATH_SLASH;
1989 memcpy(&szPath[cchBefore + cchLink + 1],
1990 &Info.szPath[Info.aoffComponents[iComponent + 1]],
1991 cchAfter); /* cchAfter includes a zero terminator */
1992 }
1993
1994 /* Parse, copy and check the sanity (no '..' or '.') of the altered path. */
1995 rc = supR3HardenedVerifyPathSanity(szPath, pErrInfo, &Info);
1996 if (RT_FAILURE(rc))
1997 return rc;
1998 if (Info.fDirSlash)
1999 return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo,
2000 "The file path specifies a directory: '", szPath, "'");
2001
2002 /* Restart from the current component. */
2003 continue;
2004 }
2005 int iErr = errno;
2006 supR3HardenedError(VERR_ACCESS_DENIED, false /*fFatal*/,
2007 "supR3HardenedVerifyFileFollowSymlinks: Failed to readlink '%s': %s (%d)\n",
2008 Info.szPath, strerror(iErr), iErr);
2009 return supR3HardenedSetError4(VERR_ACCESS_DENIED, pErrInfo,
2010 "readlink failed for '", Info.szPath, "': ", strerror(iErr));
2011 }
2012 return supR3HardenedSetError(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo, "Path too long for symlink replacing!");
2013 }
2014 else
2015 return supR3HardenedSetError3(VERR_TOO_MANY_SYMLINKS, pErrInfo,
2016 "Too many symbolic links: '", pszFilename, "'");
2017#endif
2018 }
2019 }
2020 if (RT_FAILURE(rc))
2021 return rc;
2022 Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = !fFinal ? RTPATH_SLASH : '\0';
2023 iComponent++;
2024 }
2025
2026 /*
2027 * Verify the file handle against the last component, if specified.
2028 */
2029 if (hNativeFile != RTHCUINTPTR_MAX)
2030 {
2031 rc = supR3HardenedVerifySameFsObject(hNativeFile, &FsObjState, Info.szPath, pErrInfo);
2032 if (RT_FAILURE(rc))
2033 return rc;
2034 }
2035
2036 return VINF_SUCCESS;
2037}
2038#endif /* RT_OS_DARWIN || RT_OS_LINUX */
2039
2040
2041/**
2042 * Gets the pre-init data for the hand-over to the other version
2043 * of this code.
2044 *
2045 * The reason why we pass this information on is that it contains
2046 * open directories and files. Later it may include even more info
2047 * (int the verified arrays mostly).
2048 *
2049 * The receiver is supR3HardenedRecvPreInitData.
2050 *
2051 * @param pPreInitData Where to store it.
2052 */
2053DECLHIDDEN(void) supR3HardenedGetPreInitData(PSUPPREINITDATA pPreInitData)
2054{
2055 pPreInitData->cInstallFiles = RT_ELEMENTS(g_aSupInstallFiles);
2056 pPreInitData->paInstallFiles = &g_aSupInstallFiles[0];
2057 pPreInitData->paVerifiedFiles = &g_aSupVerifiedFiles[0];
2058
2059 pPreInitData->cVerifiedDirs = RT_ELEMENTS(g_aSupVerifiedDirs);
2060 pPreInitData->paVerifiedDirs = &g_aSupVerifiedDirs[0];
2061}
2062
2063
2064/**
2065 * Receives the pre-init data from the static executable stub.
2066 *
2067 * @returns VBox status code. Will not bitch on failure since the
2068 * runtime isn't ready for it, so that is left to the exe stub.
2069 *
2070 * @param pPreInitData The hand-over data.
2071 */
2072DECLHIDDEN(int) supR3HardenedRecvPreInitData(PCSUPPREINITDATA pPreInitData)
2073{
2074 /*
2075 * Compare the array lengths and the contents of g_aSupInstallFiles.
2076 */
2077 if ( pPreInitData->cInstallFiles != RT_ELEMENTS(g_aSupInstallFiles)
2078 || pPreInitData->cVerifiedDirs != RT_ELEMENTS(g_aSupVerifiedDirs))
2079 return VERR_VERSION_MISMATCH;
2080 SUPINSTFILE const *paInstallFiles = pPreInitData->paInstallFiles;
2081 for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++)
2082 if ( g_aSupInstallFiles[iFile].enmDir != paInstallFiles[iFile].enmDir
2083 || g_aSupInstallFiles[iFile].enmType != paInstallFiles[iFile].enmType
2084 || g_aSupInstallFiles[iFile].fOptional != paInstallFiles[iFile].fOptional
2085 || suplibHardenedStrCmp(g_aSupInstallFiles[iFile].pszFile, paInstallFiles[iFile].pszFile))
2086 return VERR_VERSION_MISMATCH;
2087
2088 /*
2089 * Check that we're not called out of order.
2090 * If dynamic linking it screwed up, we may end up here.
2091 */
2092 if ( !ASMMemIsZero(&g_aSupVerifiedFiles[0], sizeof(g_aSupVerifiedFiles))
2093 || !ASMMemIsZero(&g_aSupVerifiedDirs[0], sizeof(g_aSupVerifiedDirs)))
2094 return VERR_WRONG_ORDER;
2095
2096 /*
2097 * Copy the verification data over.
2098 */
2099 suplibHardenedMemCopy(&g_aSupVerifiedFiles[0], pPreInitData->paVerifiedFiles, sizeof(g_aSupVerifiedFiles));
2100 suplibHardenedMemCopy(&g_aSupVerifiedDirs[0], pPreInitData->paVerifiedDirs, sizeof(g_aSupVerifiedDirs));
2101 return VINF_SUCCESS;
2102}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use