/** * Loads the DLL/SO/DYLIB containing the actual program and * resolves the TrustedMain symbol. * * @returns Pointer to the trusted main of the actual program. * @param pszProgName The program name. * @remarks This function will not return on failure. */ static PFNSUPTRUSTEDMAIN supR3HardenedMainGetTrustedMain(const char *pszProgName) { /* * Construct the name. */ char szPath[RTPATH_MAX]; supR3HardenedPathAppPrivateArch(szPath, sizeof(szPath) - 10); size_t cch = strlen(szPath); supR3HardenedStrPrintf(&szPath[cch], sizeof(szPath) - cch, "/%s%s", pszProgName, SUPLIB_DLL_SUFF); /* * Open it and resolve the symbol. */ #if defined(RT_OS_WINDOWS) /** @todo consider using LOAD_WITH_ALTERED_SEARCH_PATH here! */ HMODULE hMod = LoadLibraryEx(szPath, NULL /*hFile*/, 0 /* dwFlags */); if (!hMod) supR3HardenedFatal("supR3HardenedMainGetTrustedMain: LoadLibraryEx(\"%s\",,) failed, rc=%d\n", szPath, GetLastError()); FARPROC pfn = GetProcAddress(hMod, SUP_HARDENED_SYM("TrustedMain")); if (!pfn) supR3HardenedFatal("supR3HardenedMainGetTrustedMain: Entrypoint \"TrustedMain\" not found in \"%s\" (rc=%d)\n", szPath, GetLastError()); return (PFNSUPTRUSTEDMAIN)pfn; #else /* the dlopen crowd */ void *pvMod = dlopen(szPath, RTLD_NOW | RTLD_GLOBAL); if (!pvMod) supR3HardenedFatal("supR3HardenedMainGetTrustedMain: dlopen(\"%s\",) failed: %s\n", szPath, dlerror()); void *pvSym = dlsym(pvMod, SUP_HARDENED_SYM("TrustedMain")); if (!pvSym) supR3HardenedFatal("supR3HardenedMainGetTrustedMain: Entrypoint \"TrustedMain\" not found in \"%s\"!\ndlerror: %s\n", szPath, dlerror()); return (PFNSUPTRUSTEDMAIN)(uintptr_t)pvSym; #endif }
/** * Wrapper around snprintf which will throw a fatal error on buffer overflow. * * @returns Number of chars in the result string. * @param pszDst The destination buffer. * @param cchDst The size of the buffer. * @param pszFormat The format string. * @param ... Format arguments. */ static size_t supR3HardenedStrPrintf(char *pszDst, size_t cchDst, const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); #ifdef _MSC_VER int cch = _vsnprintf(pszDst, cchDst, pszFormat, va); #else int cch = vsnprintf(pszDst, cchDst, pszFormat, va); #endif va_end(va); if ((unsigned)cch >= cchDst || cch < 0) supR3HardenedFatal("supR3HardenedStrPrintf: buffer overflow, %d >= %lu\n", cch, (long)cchDst); return cch; }
/** * @copydoc RTPathAppDocs */ DECLHIDDEN(int) supR3HardenedPathAppDocs(char *pszPath, size_t cchPath) { #if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_DOCS) const char *pszSrcPath = RTPATH_APP_DOCS; size_t cchPathAppDocs = strlen(pszSrcPath); if (cchPathAppDocs >= cchPath) supR3HardenedFatal("supR3HardenedPathAppDocs: Buffer overflow, %lu >= %lu\n", (unsigned long)cchPathAppDocs, (unsigned long)cchPath); memcpy(pszPath, pszSrcPath, cchPathAppDocs + 1); return VINF_SUCCESS; #else return supR3HardenedPathExecDir(pszPath, cchPath); #endif }
/** * @copydoc RTPathAppPrivateArch */ DECLHIDDEN(int) supR3HardenedPathAppPrivateArch(char *pszPath, size_t cchPath) { #if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_PRIVATE_ARCH) const char *pszSrcPath = RTPATH_APP_PRIVATE_ARCH; size_t cchPathPrivateArch = strlen(pszSrcPath); if (cchPathPrivateArch >= cchPath) supR3HardenedFatal("supR3HardenedPathAppPrivateArch: Buffer overflow, %lu >= %lu\n", (unsigned long)cchPathPrivateArch, (unsigned long)cchPath); memcpy(pszPath, pszSrcPath, cchPathPrivateArch + 1); return VINF_SUCCESS; #else return supR3HardenedPathExecDir(pszPath, cchPath); #endif }
/** * Gets the address of a procedure in a DLL, ignoring our own syscall * implementations. * * Currently restricted to NTDLL and KERNEL32 * * @returns The procedure address. * @param pszDll The DLL name. * @param pszProcedure The procedure name. */ DECLHIDDEN(PFNRT) supR3HardenedWinGetRealDllSymbol(const char *pszDll, const char *pszProcedure) { /* * Look the DLL up in the import DLL table. */ for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) if (RTStrICmp(g_aSupNtImpDlls[iDll].pszName, pszDll) == 0) { PSUPHNTLDRCACHEENTRY pLdrEntry; int rc = supHardNtLdrCacheOpen(g_aSupNtImpDlls[iDll].pszName, &pLdrEntry); if (RT_SUCCESS(rc)) { uint8_t *pbBits; rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, NULL, NULL, NULL /*pErrInfo*/); if (RT_SUCCESS(rc)) { RTLDRADDR uValue; rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, UINT32_MAX, pszProcedure, &uValue); if (RT_SUCCESS(rc)) return (PFNRT)(uintptr_t)uValue; SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: Error getting %s in %s -> %Rrc\n", pszProcedure, pszDll, rc)); } else SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: supHardNtLdrCacheEntryAllocBits failed on %s: %Rrc\n", pszDll, rc)); } else SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: supHardNtLdrCacheOpen failed on %s: %Rrc\n", pszDll, rc)); /* Complications, just call GetProcAddress. */ if (g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) return (PFNRT)GetProcAddress(GetModuleHandleW(g_aSupNtImpDlls[iDll].pwszName), pszProcedure); return NULL; } supR3HardenedFatal("supR3HardenedWinGetRealDllSymbol: Unknown DLL %s (proc: %s)\n", pszDll, pszProcedure); return NULL; }
/** * @copydoc RTPathExecDir */ DECLHIDDEN(int) supR3HardenedPathExecDir(char *pszPath, size_t cchPath) { /* * Lazy init (probably not required). */ if (!g_szSupLibHardenedDirPath[0]) supR3HardenedGetFullExePath(); /* * Calc the length and check if there is space before copying. */ size_t cch = strlen(g_szSupLibHardenedDirPath) + 1; if (cch <= cchPath) { memcpy(pszPath, g_szSupLibHardenedDirPath, cch + 1); return VINF_SUCCESS; } supR3HardenedFatal("supR3HardenedPathExecDir: Buffer too small (%u < %u)\n", cchPath, cch); return VERR_BUFFER_OVERFLOW; }
/** * Drop any root privileges we might be holding. */ static void supR3HardenedMainDropPrivileges(void) { /* * Try use setre[ug]id since this will clear the save uid/gid and thus * leave fewer traces behind that libs like GTK+ may pick up. */ uid_t euid, ruid, suid; gid_t egid, rgid, sgid; # if defined(RT_OS_DARWIN) /* The really great thing here is that setreuid isn't available on OS X 10.4, libc emulates it. While 10.4 have a slightly different and non-standard setuid implementation compared to 10.5, the following works the same way with both version since we're super user (10.5 req). The following will set all three variants of the group and user IDs. */ setgid(g_gid); setuid(g_uid); euid = geteuid(); ruid = suid = getuid(); egid = getegid(); rgid = sgid = getgid(); # elif defined(RT_OS_SOLARIS) /* Solaris doesn't have setresuid, but the setreuid interface is BSD compatible and will set the saved uid to euid when we pass it a ruid that isn't -1 (which we do). */ setregid(g_gid, g_gid); setreuid(g_uid, g_uid); euid = geteuid(); ruid = suid = getuid(); egid = getegid(); rgid = sgid = getgid(); # else /* This is the preferred one, full control no questions about semantics. PORTME: If this isn't work, try join one of two other gangs above. */ setresgid(g_gid, g_gid, g_gid); setresuid(g_uid, g_uid, g_uid); if (getresuid(&ruid, &euid, &suid) != 0) { euid = geteuid(); ruid = suid = getuid(); } if (getresgid(&rgid, &egid, &sgid) != 0) { egid = getegid(); rgid = sgid = getgid(); } # endif /* Check that it worked out all right. */ if ( euid != g_uid || ruid != g_uid || suid != g_uid || egid != g_gid || rgid != g_gid || sgid != g_gid) supR3HardenedFatal("SUPR3HardenedMain: failed to drop root privileges!" " (euid=%d ruid=%d suid=%d egid=%d rgid=%d sgid=%d; wanted uid=%d and gid=%d)\n", euid, ruid, suid, egid, rgid, sgid, g_uid, g_gid); # if RT_OS_LINUX /* * Re-enable the cap_net_raw capability which was disabled during setresuid. */ if (g_uCaps != 0) { # ifdef USE_LIB_PCAP /** @todo Warn if that does not work? */ /* XXX cap_net_bind_service */ cap_set_proc(cap_from_text("cap_net_raw+ep")); # else cap_user_header_t hdr = (cap_user_header_t)alloca(sizeof(*hdr)); cap_user_data_t cap = (cap_user_data_t)alloca(sizeof(*cap)); memset(hdr, 0, sizeof(*hdr)); hdr->version = _LINUX_CAPABILITY_VERSION; memset(cap, 0, sizeof(*cap)); cap->effective = g_uCaps; cap->permitted = g_uCaps; /** @todo Warn if that does not work? */ capset(hdr, cap); # endif /* !USE_LIB_PCAP */ } # endif }
/** * Returns the full path to the executable. * * @returns IPRT status code. * @param pszPath Where to store it. * @param cchPath How big that buffer is. */ static void supR3HardenedGetFullExePath(void) { /* * Get the program filename. * * Most UNIXes have no API for obtaining the executable path, but provides a symbolic * link in the proc file system that tells who was exec'ed. The bad thing about this * is that we have to use readlink, one of the weirder UNIX APIs. * * Darwin, OS/2 and Windows all have proper APIs for getting the program file name. */ #if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_SOLARIS) # ifdef RT_OS_LINUX int cchLink = readlink("/proc/self/exe", &g_szSupLibHardenedExePath[0], sizeof(g_szSupLibHardenedExePath) - 1); # elif defined(RT_OS_SOLARIS) char szFileBuf[PATH_MAX + 1]; sprintf(szFileBuf, "/proc/%ld/path/a.out", (long)getpid()); int cchLink = readlink(szFileBuf, &g_szSupLibHardenedExePath[0], sizeof(g_szSupLibHardenedExePath) - 1); # else /* RT_OS_FREEBSD */ int aiName[4]; aiName[0] = CTL_KERN; aiName[1] = KERN_PROC; aiName[2] = KERN_PROC_PATHNAME; aiName[3] = getpid(); size_t cbPath = sizeof(g_szSupLibHardenedExePath); if (sysctl(aiName, RT_ELEMENTS(aiName), g_szSupLibHardenedExePath, &cbPath, NULL, 0) < 0) supR3HardenedFatal("supR3HardenedExecDir: sysctl failed\n"); g_szSupLibHardenedExePath[sizeof(g_szSupLibHardenedExePath) - 1] = '\0'; int cchLink = strlen(g_szSupLibHardenedExePath); /* paranoid? can't we use cbPath? */ # endif if (cchLink < 0 || cchLink == sizeof(g_szSupLibHardenedExePath) - 1) supR3HardenedFatal("supR3HardenedExecDir: couldn't read \"%s\", errno=%d cchLink=%d\n", g_szSupLibHardenedExePath, errno, cchLink); g_szSupLibHardenedExePath[cchLink] = '\0'; #elif defined(RT_OS_OS2) || defined(RT_OS_L4) _execname(g_szSupLibHardenedExePath, sizeof(g_szSupLibHardenedExePath)); #elif defined(RT_OS_DARWIN) const char *pszImageName = _dyld_get_image_name(0); if (!pszImageName) supR3HardenedFatal("supR3HardenedExecDir: _dyld_get_image_name(0) failed\n"); size_t cchImageName = strlen(pszImageName); if (!cchImageName || cchImageName >= sizeof(g_szSupLibHardenedExePath)) supR3HardenedFatal("supR3HardenedExecDir: _dyld_get_image_name(0) failed, cchImageName=%d\n", cchImageName); memcpy(g_szSupLibHardenedExePath, pszImageName, cchImageName + 1); #elif defined(RT_OS_WINDOWS) HMODULE hExe = GetModuleHandle(NULL); if (!GetModuleFileName(hExe, &g_szSupLibHardenedExePath[0], sizeof(g_szSupLibHardenedExePath))) supR3HardenedFatal("supR3HardenedExecDir: GetModuleFileName failed, rc=%d\n", GetLastError()); #else # error needs porting. #endif /* * Strip off the filename part (RTPathStripFilename()). */ strcpy(g_szSupLibHardenedDirPath, g_szSupLibHardenedExePath); suplibHardenedPathStripFilename(g_szSupLibHardenedDirPath); }