VirtualBox

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

Last change on this file since 33874 was 33874, checked in by vboxsync, 14 years ago

typo

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