VirtualBox

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

Last change on this file since 46861 was 44580, checked in by vboxsync, 12 years ago

VBoxBFE: RIP.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette