rufus/src/stdfn.c

1116 lines
33 KiB
C
Raw Normal View History

/*
* Rufus: The Reliable USB Formatting Utility
* Standard Windows function calls
* Copyright © 2013-2021 Pete Batard <pete@akeo.ie>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif
#include <windows.h>
#include <sddl.h>
#include <gpedit.h>
#include <assert.h>
#include "rufus.h"
#include "missing.h"
#include "resource.h"
#include "msapi_utf8.h"
#include "localization.h"
#include "settings.h"
int nWindowsVersion = WINDOWS_UNDEFINED;
int nWindowsBuildNumber = -1;
int nWindowsEdition = 0;
char WindowsVersionStr[128] = "Windows ";
/*
* Hash table functions - modified From glibc 2.3.2:
* [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986
* [Knuth] The Art of Computer Programming, part 3 (6.4)
*/
/*
* For the used double hash method the table size has to be a prime. To
* correct the user given table size we need a prime test. This trivial
* algorithm is adequate because the code is called only during init and
* the number is likely to be small
*/
static uint32_t isprime(uint32_t number)
{
// no even number will be passed
uint32_t divider = 3;
while((divider * divider < number) && (number % divider != 0))
divider += 2;
return (number % divider != 0);
}
/*
* Before using the hash table we must allocate memory for it.
* We allocate one element more as the found prime number says.
* This is done for more effective indexing as explained in the
* comment for the hash function.
*/
BOOL htab_create(uint32_t nel, htab_table* htab)
{
if (htab == NULL) {
return FALSE;
}
if (htab->table != NULL) {
uprintf("warning: htab_create() was called with a non empty table");
return FALSE;
}
// Change nel to the first prime number not smaller as nel.
nel |= 1;
while(!isprime(nel))
nel += 2;
htab->size = nel;
htab->filled = 0;
// allocate memory and zero out.
htab->table = (htab_entry*)calloc(htab->size + 1, sizeof(htab_entry));
if (htab->table == NULL) {
uprintf("could not allocate space for hash table\n");
return FALSE;
}
return TRUE;
}
/* After using the hash table it has to be destroyed. */
void htab_destroy(htab_table* htab)
{
size_t i;
if ((htab == NULL) || (htab->table == NULL)) {
return;
}
for (i=0; i<htab->size+1; i++) {
if (htab->table[i].used) {
safe_free(htab->table[i].str);
}
}
htab->filled = 0; htab->size = 0;
safe_free(htab->table);
htab->table = NULL;
}
/*
* This is the search function. It uses double hashing with open addressing.
* We use a trick to speed up the lookup. The table is created with one
* more element available. This enables us to use the index zero special.
* This index will never be used because we store the first hash index in
* the field used where zero means not used. Every other value means used.
* The used field can be used as a first fast comparison for equality of
* the stored and the parameter value. This helps to prevent unnecessary
* expensive calls of strcmp.
*/
uint32_t htab_hash(char* str, htab_table* htab)
{
uint32_t hval, hval2;
uint32_t idx;
uint32_t r = 0;
int c;
char* sz = str;
if ((htab == NULL) || (htab->table == NULL) || (str == NULL)) {
return 0;
}
// Compute main hash value using sdbm's algorithm (empirically
// shown to produce half the collisions as djb2's).
// See http://www.cse.yorku.ca/~oz/hash.html
while ((c = *sz++) != 0)
r = c + (r << 6) + (r << 16) - r;
if (r == 0)
++r;
// compute table hash: simply take the modulus
hval = r % htab->size;
if (hval == 0)
++hval;
// Try the first index
idx = hval;
if (htab->table[idx].used) {
if ( (htab->table[idx].used == hval)
&& (safe_strcmp(str, htab->table[idx].str) == 0) ) {
// existing hash
return idx;
}
// uprintf("hash collision ('%s' vs '%s')\n", str, htab->table[idx].str);
// Second hash function, as suggested in [Knuth]
hval2 = 1 + hval % (htab->size - 2);
do {
// Because size is prime this guarantees to step through all available indexes
if (idx <= hval2) {
idx = ((uint32_t)htab->size) + idx - hval2;
} else {
idx -= hval2;
}
// If we visited all entries leave the loop unsuccessfully
if (idx == hval) {
break;
}
// If entry is found use it.
if ( (htab->table[idx].used == hval)
&& (safe_strcmp(str, htab->table[idx].str) == 0) ) {
return idx;
}
}
while (htab->table[idx].used);
}
// Not found => New entry
// If the table is full return an error
if (htab->filled >= htab->size) {
uprintf("hash table is full (%d entries)", htab->size);
return 0;
}
safe_free(htab->table[idx].str);
htab->table[idx].used = hval;
htab->table[idx].str = (char*) malloc(safe_strlen(str)+1);
if (htab->table[idx].str == NULL) {
uprintf("could not duplicate string for hash table\n");
return 0;
}
memcpy(htab->table[idx].str, str, safe_strlen(str)+1);
++htab->filled;
return idx;
}
BOOL is_x64(void)
{
BOOL ret = FALSE;
PF_TYPE_DECL(WINAPI, BOOL, IsWow64Process, (HANDLE, PBOOL));
// Detect if we're running a 32 or 64 bit system
if (sizeof(uintptr_t) < 8) {
PF_INIT(IsWow64Process, Kernel32);
if (pfIsWow64Process != NULL) {
(*pfIsWow64Process)(GetCurrentProcess(), &ret);
}
} else {
ret = TRUE;
}
return ret;
}
int GetCpuArch(void)
{
SYSTEM_INFO info = { 0 };
GetNativeSystemInfo(&info);
switch (info.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_AMD64:
return CPU_ARCH_X86_64;
case PROCESSOR_ARCHITECTURE_INTEL:
return CPU_ARCH_X86_64;
case PROCESSOR_ARCHITECTURE_ARM64:
return CPU_ARCH_ARM_64;
case PROCESSOR_ARCHITECTURE_ARM:
return CPU_ARCH_ARM_32;
default:
return CPU_ARCH_UNDEFINED;
}
}
static const char* GetEdition(DWORD ProductType)
{
static char unknown_edition_str[64];
// From: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getproductinfo
// These values can be found in the winnt.h header.
switch (ProductType) {
case 0x00000000: return ""; // Undefined
case 0x00000001: return "Ultimate";
case 0x00000002: return "Home Basic";
case 0x00000003: return "Home Premium";
case 0x00000004: return "Enterprise";
case 0x00000005: return "Home Basic N";
case 0x00000006: return "Business";
case 0x00000007: return "Server Standard";
case 0x00000008: return "Server Datacenter";
case 0x00000009: return "Smallbusiness Server";
case 0x0000000A: return "Server Enterprise";
case 0x0000000B: return "Starter";
case 0x0000000C: return "Server Datacenter (Core)";
case 0x0000000D: return "Server Standard (Core)";
case 0x0000000E: return "Server Enterprise (Core)";
case 0x00000010: return "Business N";
case 0x00000011: return "Web Server";
case 0x00000012: return "HPC Edition";
case 0x00000013: return "Storage Server (Essentials)";
case 0x0000001A: return "Home Premium N";
case 0x0000001B: return "Enterprise N";
case 0x0000001C: return "Ultimate N";
case 0x00000022: return "Home Server";
case 0x00000024: return "Server Standard without Hyper-V";
case 0x00000025: return "Server Datacenter without Hyper-V";
case 0x00000026: return "Server Enterprise without Hyper-V";
case 0x00000027: return "Server Datacenter without Hyper-V (Core)";
case 0x00000028: return "Server Standard without Hyper-V (Core)";
case 0x00000029: return "Server Enterprise without Hyper-V (Core)";
case 0x0000002A: return "Hyper-V Server";
case 0x0000002F: return "Starter N";
case 0x00000030: return "Pro";
case 0x00000031: return "Pro N";
case 0x00000034: return "Server Solutions Premium";
case 0x00000035: return "Server Solutions Premium (Core)";
case 0x00000040: return "Server Hyper Core V";
case 0x00000042: return "Starter E";
case 0x00000043: return "Home Basic E";
case 0x00000044: return "Premium E";
case 0x00000045: return "Pro E";
case 0x00000046: return "Enterprise E";
case 0x00000047: return "Ultimate E";
case 0x00000048: return "Enterprise (Eval)";
case 0x0000004F: return "Server Standard (Eval)";
case 0x00000050: return "Server Datacenter (Eval)";
case 0x00000054: return "Enterprise N (Eval)";
case 0x00000057: return "Thin PC";
case 0x00000058: case 0x00000059: case 0x0000005A: case 0x0000005B: case 0x0000005C: return "Embedded";
case 0x00000062: return "Home N";
case 0x00000063: return "Home China";
case 0x00000064: return "Home Single Language";
case 0x00000065: return "Home";
case 0x00000067: return "Pro with Media Center";
case 0x00000069: case 0x0000006A: case 0x0000006B: case 0x0000006C: return "Embedded";
case 0x0000006F: return "Home Connected";
case 0x00000070: return "Pro Student";
case 0x00000071: return "Home Connected N";
case 0x00000072: return "Pro Student N";
case 0x00000073: return "Home Connected Single Language";
case 0x00000074: return "Home Connected China";
case 0x00000079: return "Education";
case 0x0000007A: return "Education N";
case 0x0000007D: return "Enterprise LTSB";
case 0x0000007E: return "Enterprise LTSB N";
case 0x0000007F: return "Pro S";
case 0x00000080: return "Pro S N";
case 0x00000081: return "Enterprise LTSB (Eval)";
case 0x00000082: return "Enterprise LTSB N (Eval)";
case 0x0000008A: return "Pro Single Language";
case 0x0000008B: return "Pro China";
case 0x0000008C: return "Enterprise Subscription";
case 0x0000008D: return "Enterprise Subscription N";
case 0x00000091: return "Server Datacenter SA (Core)";
case 0x00000092: return "Server Standard SA (Core)";
case 0x00000095: return "Utility VM";
case 0x000000A1: return "Pro for Workstations";
case 0x000000A2: return "Pro for Workstations N";
case 0x000000A4: return "Pro for Education";
case 0x000000A5: return "Pro for Education N";
case 0x000000AB: return "Enterprise G"; // I swear Microsoft are just making up editions...
case 0x000000AC: return "Enterprise G N";
case 0x000000B6: return "Home OS";
case 0x000000B7: return "Cloud E";
case 0x000000B8: return "Cloud E N";
case 0x000000BD: return "Lite";
case 0xABCDABCD: return "(Unlicensed)";
default:
static_sprintf(unknown_edition_str, "(Unknown Edition 0x%02X)", (uint32_t)ProductType);
return unknown_edition_str;
}
}
/*
* Modified from smartmontools' os_win32.cpp
*/
void GetWindowsVersion(void)
{
OSVERSIONINFOEXA vi, vi2;
DWORD dwProductType = 0;
const char* w = 0;
const char* w64 = "32 bit";
char *vptr;
size_t vlen;
unsigned major, minor;
ULONGLONG major_equal, minor_equal;
BOOL ws;
nWindowsVersion = WINDOWS_UNDEFINED;
static_strcpy(WindowsVersionStr, "Windows Undefined");
memset(&vi, 0, sizeof(vi));
vi.dwOSVersionInfoSize = sizeof(vi);
if (!GetVersionExA((OSVERSIONINFOA *)&vi)) {
memset(&vi, 0, sizeof(vi));
vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
if (!GetVersionExA((OSVERSIONINFOA *)&vi))
return;
}
if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
if (vi.dwMajorVersion > 6 || (vi.dwMajorVersion == 6 && vi.dwMinorVersion >= 2)) {
// Starting with Windows 8.1 Preview, GetVersionEx() does no longer report the actual OS version
// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074.aspx
// And starting with Windows 10 Preview 2, Windows enforces the use of the application/supportedOS
// manifest in order for VerSetConditionMask() to report the ACTUAL OS major and minor...
major_equal = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL);
for (major = vi.dwMajorVersion; major <= 9; major++) {
memset(&vi2, 0, sizeof(vi2));
vi2.dwOSVersionInfoSize = sizeof(vi2); vi2.dwMajorVersion = major;
if (!VerifyVersionInfoA(&vi2, VER_MAJORVERSION, major_equal))
continue;
if (vi.dwMajorVersion < major) {
vi.dwMajorVersion = major; vi.dwMinorVersion = 0;
}
minor_equal = VerSetConditionMask(0, VER_MINORVERSION, VER_EQUAL);
for (minor = vi.dwMinorVersion; minor <= 9; minor++) {
memset(&vi2, 0, sizeof(vi2)); vi2.dwOSVersionInfoSize = sizeof(vi2);
vi2.dwMinorVersion = minor;
if (!VerifyVersionInfoA(&vi2, VER_MINORVERSION, minor_equal))
continue;
vi.dwMinorVersion = minor;
break;
}
break;
}
}
if (vi.dwMajorVersion <= 0xf && vi.dwMinorVersion <= 0xf) {
ws = (vi.wProductType <= VER_NT_WORKSTATION);
nWindowsVersion = vi.dwMajorVersion << 4 | vi.dwMinorVersion;
switch (nWindowsVersion) {
case WINDOWS_XP: w = "XP";
break;
case WINDOWS_2003: w = (ws ? "XP_64" : (!GetSystemMetrics(89) ? "Server 2003" : "Server 2003_R2"));
break;
case WINDOWS_VISTA: w = (ws ? "Vista" : "Server 2008");
break;
case WINDOWS_7: w = (ws ? "7" : "Server 2008_R2");
break;
case WINDOWS_8: w = (ws ? "8" : "Server 2012");
break;
case WINDOWS_8_1: w = (ws ? "8.1" : "Server 2012_R2");
break;
case WINDOWS_10_PREVIEW1: w = (ws ? "10 (Preview 1)" : "Server 10 (Preview 1)");
break;
// Starting with Windows 10 Preview 2, the major is the same as the public-facing version
case WINDOWS_10:
if (vi.dwBuildNumber < 20000) {
w = (ws ? "10" : ((vi.dwBuildNumber < 17763) ? "Server 2016" : "Server 2019"));
break;
}
nWindowsVersion = WINDOWS_11;
// Fall through
case WINDOWS_11: w = (ws ? "11" : "Server 2022");
break;
default:
if (nWindowsVersion < WINDOWS_XP)
nWindowsVersion = WINDOWS_UNSUPPORTED;
else
w = "12 or later";
break;
}
}
}
if (is_x64())
w64 = "64-bit";
GetProductInfo(vi.dwMajorVersion, vi.dwMinorVersion, vi.wServicePackMajor, vi.wServicePackMinor, &dwProductType);
vptr = &WindowsVersionStr[sizeof("Windows ") - 1];
vlen = sizeof(WindowsVersionStr) - sizeof("Windows ") - 1;
if (!w)
safe_sprintf(vptr, vlen, "%s %u.%u %s", (vi.dwPlatformId == VER_PLATFORM_WIN32_NT ? "NT" : "??"),
(unsigned)vi.dwMajorVersion, (unsigned)vi.dwMinorVersion, w64);
else if (vi.wServicePackMinor)
safe_sprintf(vptr, vlen, "%s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, w64);
else if (vi.wServicePackMajor)
safe_sprintf(vptr, vlen, "%s SP%u %s", w, vi.wServicePackMajor, w64);
else
safe_sprintf(vptr, vlen, "%s%s%s, %s",
w, (dwProductType != 0) ? " " : "", GetEdition(dwProductType), w64);
nWindowsEdition = (int)dwProductType;
// Add the build number (including UBR if available) for Windows 8.0 and later
nWindowsBuildNumber = vi.dwBuildNumber;
if (nWindowsVersion >= 0x62) {
int nUbr = ReadRegistryKey32(REGKEY_HKLM, "Software\\Microsoft\\Windows NT\\CurrentVersion\\UBR");
vptr = &WindowsVersionStr[safe_strlen(WindowsVersionStr)];
vlen = sizeof(WindowsVersionStr) - safe_strlen(WindowsVersionStr) - 1;
if (nUbr > 0)
safe_sprintf(vptr, vlen, " (Build %d.%d)", nWindowsBuildNumber, nUbr);
else
safe_sprintf(vptr, vlen, " (Build %d)", nWindowsBuildNumber);
}
}
/*
* String array manipulation
*/
void StrArrayCreate(StrArray* arr, uint32_t initial_size)
{
if (arr == NULL) return;
arr->Max = initial_size; arr->Index = 0;
arr->String = (char**)calloc(arr->Max, sizeof(char*));
if (arr->String == NULL)
uprintf("Could not allocate string array\n");
}
int32_t StrArrayAdd(StrArray* arr, const char* str, BOOL duplicate)
{
char** old_table;
if ((arr == NULL) || (arr->String == NULL) || (str == NULL))
return -1;
if (arr->Index == arr->Max) {
arr->Max *= 2;
old_table = arr->String;
arr->String = (char**)realloc(arr->String, arr->Max*sizeof(char*));
if (arr->String == NULL) {
free(old_table);
uprintf("Could not reallocate string array\n");
return -1;
}
}
arr->String[arr->Index] = (duplicate)?safe_strdup(str):(char*)str;
if (arr->String[arr->Index] == NULL) {
uprintf("Could not store string in array\n");
return -1;
}
return arr->Index++;
}
int32_t StrArrayFind(StrArray* arr, const char* str)
{
uint32_t i;
if ((str == NULL) || (arr == NULL) || (arr->String == NULL))
return -1;
for (i = 0; i<arr->Index; i++) {
if (strcmp(arr->String[i], str) == 0)
return (int32_t)i;
}
return -1;
}
void StrArrayClear(StrArray* arr)
{
uint32_t i;
if ((arr == NULL) || (arr->String == NULL))
return;
for (i=0; i<arr->Index; i++) {
safe_free(arr->String[i]);
}
arr->Index = 0;
}
void StrArrayDestroy(StrArray* arr)
{
StrArrayClear(arr);
if (arr != NULL)
safe_free(arr->String);
}
/*
* Retrieve the SID of the current user. The returned PSID must be freed by the caller using LocalFree()
*/
static PSID GetSID(void) {
TOKEN_USER* tu = NULL;
DWORD len;
HANDLE token;
PSID ret = NULL;
char* psid_string = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
uprintf("OpenProcessToken failed: %s\n", WindowsErrorString());
return NULL;
}
if (!GetTokenInformation(token, TokenUser, tu, 0, &len)) {
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
uprintf("GetTokenInformation (pre) failed: %s\n", WindowsErrorString());
return NULL;
}
tu = (TOKEN_USER*)calloc(1, len);
}
if (tu == NULL) {
return NULL;
}
if (GetTokenInformation(token, TokenUser, tu, len, &len)) {
/*
* now of course, the interesting thing is that if you return tu->User.Sid
* but free tu, the PSID pointer becomes invalid after a while.
* The workaround? Convert to string then back to PSID
*/
if (!ConvertSidToStringSidA(tu->User.Sid, &psid_string)) {
uprintf("Unable to convert SID to string: %s\n", WindowsErrorString());
ret = NULL;
} else {
if (!ConvertStringSidToSidA(psid_string, &ret)) {
uprintf("Unable to convert string back to SID: %s\n", WindowsErrorString());
ret = NULL;
}
// MUST use LocalFree()
LocalFree(psid_string);
}
} else {
ret = NULL;
uprintf("GetTokenInformation (real) failed: %s\n", WindowsErrorString());
}
free(tu);
return ret;
}
/*
* read or write I/O to a file
* buffer is allocated by the procedure. path is UTF-8
*/
BOOL FileIO(BOOL save, char* path, char** buffer, DWORD* size)
{
SECURITY_ATTRIBUTES s_attr, *sa = NULL;
SECURITY_DESCRIPTOR s_desc;
PSID sid = NULL;
HANDLE handle;
BOOL r;
BOOL ret = FALSE;
// Change the owner from admin to regular user
sid = GetSID();
if ( (sid != NULL)
&& InitializeSecurityDescriptor(&s_desc, SECURITY_DESCRIPTOR_REVISION)
&& SetSecurityDescriptorOwner(&s_desc, sid, FALSE) ) {
s_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
s_attr.bInheritHandle = FALSE;
s_attr.lpSecurityDescriptor = &s_desc;
sa = &s_attr;
} else {
uprintf("Could not set security descriptor: %s\n", WindowsErrorString());
}
if (!save) {
*buffer = NULL;
}
handle = CreateFileU(path, save?GENERIC_WRITE:GENERIC_READ, FILE_SHARE_READ,
sa, save?CREATE_ALWAYS:OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE) {
uprintf("Could not %s file '%s'\n", save?"create":"open", path);
goto out;
}
if (save) {
r = WriteFile(handle, *buffer, *size, size, NULL);
} else {
*size = GetFileSize(handle, NULL);
*buffer = (char*)malloc(*size);
if (*buffer == NULL) {
uprintf("Could not allocate buffer for reading file\n");
goto out;
}
r = ReadFile(handle, *buffer, *size, size, NULL);
}
if (!r) {
uprintf("I/O Error: %s\n", WindowsErrorString());
goto out;
}
PrintInfoDebug(0, save?MSG_216:MSG_215, path);
ret = TRUE;
out:
CloseHandle(handle);
if (!ret) {
// Only leave a buffer allocated if successful
*size = 0;
if (!save) {
safe_free(*buffer);
}
}
return ret;
}
/*
* Get a resource from the RC. If needed that resource can be duplicated.
* If duplicate is true and len is non-zero, the a zeroed buffer of 'len'
* size is allocated for the resource. Else the buffer is allocate for
* the resource size.
*/
unsigned char* GetResource(HMODULE module, char* name, char* type, const char* desc, DWORD* len, BOOL duplicate)
{
HGLOBAL res_handle;
HRSRC res;
DWORD res_len;
unsigned char* p = NULL;
res = FindResourceA(module, name, type);
if (res == NULL) {
uprintf("Could not locate resource '%s': %s\n", desc, WindowsErrorString());
goto out;
}
res_handle = LoadResource(module, res);
if (res_handle == NULL) {
uprintf("Could not load resource '%s': %s\n", desc, WindowsErrorString());
goto out;
}
res_len = SizeofResource(module, res);
if (duplicate) {
if (*len == 0)
*len = res_len;
p = (unsigned char*)calloc(*len, 1);
if (p == NULL) {
uprintf("Could not allocate resource '%s'\n", desc);
goto out;
}
memcpy(p, LockResource(res_handle), min(res_len, *len));
if (res_len > *len)
uprintf("WARNING: Resource '%s' was truncated by %d bytes!\n", desc, res_len - *len);
} else {
p = (unsigned char*)LockResource(res_handle);
}
*len = res_len;
out:
return p;
}
DWORD GetResourceSize(HMODULE module, char* name, char* type, const char* desc)
{
DWORD len = 0;
return (GetResource(module, name, type, desc, &len, FALSE) == NULL)?0:len;
}
// Run a console command, with optional redirection of stdout and stderr to our log
DWORD RunCommand(const char* cmd, const char* dir, BOOL log)
{
DWORD ret, dwRead, dwAvail, dwPipeSize = 4096;
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
HANDLE hOutputRead = INVALID_HANDLE_VALUE, hOutputWrite = INVALID_HANDLE_VALUE;
static char* output;
si.cb = sizeof(si);
if (log) {
// NB: The size of a pipe is a suggestion, NOT an absolute guarantee
// This means that you may get a pipe of 4K even if you requested 1K
if (!CreatePipe(&hOutputRead, &hOutputWrite, &sa, dwPipeSize)) {
ret = GetLastError();
uprintf("Could not set commandline pipe: %s", WindowsErrorString());
goto out;
}
[net] add Windows retail ISO downloads * This is accomplished through Fido (https://github.com/pbatard/Fido), a *SIGNED* PowerShell script, that is downloaded from GitHub and that resides in memory for the duration of a session. * The reason we use a downloaded PS script, rather than an embedded on, is because: - Microsoft have regularly been changing the deal with regards to how retail ISOs can be downloaded, and not for the better, so we can't simply embed a static means of downloading ISOs and expect that to work forever. - By using an external script, we can immediately respond to whatever new means of *ANNOYING* their legitimate users Microsoft will come up with next, as well as make sure that, the minute a new retail version of Windows becomes available, it also becomes available for download in Rufus. * Note that if you are concerned about downloading a remote PS script that is being run at the same level as an elevated application, you should understand that: - Only scripts downloaded from GitHub, from an account that is protected with 2FA, are allowed to run (i.e. someone would first have to steal a *physical* 2FA key to be in a position to upload a malicious script). - On top of this, only scripts that are signed with a separate private key (RSA + AES-256), that is itself also protected with a strong unique password which only a single person knows (and must manually enter each time they want to make a new version of the script available for download), are allowed to run. The above means that there's about as much chance for someone to manage to upload a malicious script on the GitHub servers, that Rufus would allow to run, as there is for someone to upload a malicious version of Rufus itself. Still, if you are paranoid and have concerns that, even as you can validate from its source that Rufus does not attempt to execute any remote script unless a user actively selected and clicked the DOWNLOAD button, you can also completely disable the remote script download feature, if you just set the update check to disabled (which, by the way, Rufus *EXPLICITLY* asks you to choose whether you want to enable or not, the very first time you run the application). * Also remove _unlinkU() which duplicates what DeleteFileU() already does.
2019-03-02 23:28:56 +00:00
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES | STARTF_PREVENTPINNING | STARTF_TITLEISAPPID;
si.wShowWindow = SW_HIDE;
si.hStdOutput = hOutputWrite;
si.hStdError = hOutputWrite;
}
if (!CreateProcessU(NULL, cmd, NULL, NULL, TRUE,
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, dir, &si, &pi)) {
ret = GetLastError();
uprintf("Unable to launch command '%s': %s", cmd, WindowsErrorString());
goto out;
}
if (log) {
while (1) {
// coverity[string_null]
if (PeekNamedPipe(hOutputRead, NULL, dwPipeSize, NULL, &dwAvail, NULL)) {
if (dwAvail != 0) {
output = malloc(dwAvail + 1);
if ((output != NULL) && (ReadFile(hOutputRead, output, dwAvail, &dwRead, NULL)) && (dwRead != 0)) {
output[dwAvail] = 0;
// coverity[tainted_string]
uprintf(output);
}
free(output);
}
}
if (WaitForSingleObject(pi.hProcess, 0) == WAIT_OBJECT_0)
break;
Sleep(100);
};
} else {
WaitForSingleObject(pi.hProcess, INFINITE);
}
if (!GetExitCodeProcess(pi.hProcess, &ret))
ret = GetLastError();
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
out:
safe_closehandle(hOutputWrite);
safe_closehandle(hOutputRead);
return ret;
}
BOOL CompareGUID(const GUID *guid1, const GUID *guid2) {
if ((guid1 != NULL) && (guid2 != NULL)) {
return (memcmp(guid1, guid2, sizeof(GUID)) == 0);
}
return FALSE;
}
static BOOL CALLBACK EnumFontFamExProc(const LOGFONTA *lpelfe,
const TEXTMETRICA *lpntme, DWORD FontType, LPARAM lParam)
{
return TRUE;
}
BOOL IsFontAvailable(const char* font_name)
{
BOOL r;
LOGFONTA lf = { 0 };
HDC hDC = GetDC(hMainDialog);
if (font_name == NULL) {
safe_release_dc(hMainDialog, hDC);
return FALSE;
}
lf.lfCharSet = DEFAULT_CHARSET;
safe_strcpy(lf.lfFaceName, LF_FACESIZE, font_name);
r = EnumFontFamiliesExA(hDC, &lf, EnumFontFamExProc, 0, 0);
safe_release_dc(hMainDialog, hDC);
return r;
}
/*
* Set or restore a Local Group Policy DWORD key indexed by szPath/SzPolicy
*/
// I've seen rare cases where pLGPO->lpVtbl->Save(...) gets stuck, which prevents the
// application from launching altogether. To alleviate this, use a thread that we can
// terminate if needed...
typedef struct {
BOOL bRestore;
BOOL* bExistingKey;
const char* szPath;
const char* szPolicy;
DWORD dwValue;
} SetLGP_Params;
DWORD WINAPI SetLGPThread(LPVOID param)
{
SetLGP_Params* p = (SetLGP_Params*)param;
LONG r;
DWORD disp, regtype, val=0, val_size=sizeof(DWORD);
HRESULT hr;
IGroupPolicyObject* pLGPO;
// Along with global 'existing_key', this static value is used to restore initial state
static DWORD original_val;
HKEY path_key = NULL, policy_key = NULL;
// MSVC is finicky about these ones even if you link against gpedit.lib => redefine them
const IID my_IID_IGroupPolicyObject =
{ 0xea502723L, 0xa23d, 0x11d1, { 0xa7, 0xd3, 0x0, 0x0, 0xf8, 0x75, 0x71, 0xe3 } };
const IID my_CLSID_GroupPolicyObject =
{ 0xea502722L, 0xa23d, 0x11d1, { 0xa7, 0xd3, 0x0, 0x0, 0xf8, 0x75, 0x71, 0xe3 } };
GUID ext_guid = REGISTRY_EXTENSION_GUID;
// Can be anything really
GUID snap_guid = { 0x3D271CFCL, 0x2BC6, 0x4AC2, {0xB6, 0x33, 0x3B, 0xDF, 0xF5, 0xBD, 0xAB, 0x2A} };
// Reinitialize COM since it's not shared between threads
IGNORE_RETVAL(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
// We need an IGroupPolicyObject instance to set a Local Group Policy
hr = CoCreateInstance(&my_CLSID_GroupPolicyObject, NULL, CLSCTX_INPROC_SERVER, &my_IID_IGroupPolicyObject, (LPVOID*)&pLGPO);
if (FAILED(hr)) {
ubprintf("SetLGP: CoCreateInstance failed; hr = %lx", hr);
goto error;
}
hr = pLGPO->lpVtbl->OpenLocalMachineGPO(pLGPO, GPO_OPEN_LOAD_REGISTRY);
if (FAILED(hr)) {
ubprintf("SetLGP: OpenLocalMachineGPO failed - error %lx", hr);
goto error;
}
hr = pLGPO->lpVtbl->GetRegistryKey(pLGPO, GPO_SECTION_MACHINE, &path_key);
if (FAILED(hr)) {
ubprintf("SetLGP: GetRegistryKey failed - error %lx", hr);
goto error;
}
r = RegCreateKeyExA(path_key, p->szPath, 0, NULL, 0, KEY_SET_VALUE | KEY_QUERY_VALUE,
NULL, &policy_key, &disp);
if (r != ERROR_SUCCESS) {
ubprintf("SetLGP: Failed to open LGPO path %s - error %lx", p->szPath, hr);
policy_key = NULL;
goto error;
}
if ((disp == REG_OPENED_EXISTING_KEY) && (!p->bRestore) && (!(*(p->bExistingKey)))) {
// backup existing value for restore
*(p->bExistingKey) = TRUE;
regtype = REG_DWORD;
r = RegQueryValueExA(policy_key, p->szPolicy, NULL, &regtype, (LPBYTE)&original_val, &val_size);
if (r == ERROR_FILE_NOT_FOUND) {
// The Key exists but not its value, which is OK
*(p->bExistingKey) = FALSE;
} else if (r != ERROR_SUCCESS) {
ubprintf("SetLGP: Failed to read original %s policy value - error %lx", p->szPolicy, r);
}
}
if ((!p->bRestore) || (*(p->bExistingKey))) {
val = (p->bRestore)?original_val:p->dwValue;
r = RegSetValueExA(policy_key, p->szPolicy, 0, REG_DWORD, (BYTE*)&val, sizeof(val));
} else {
r = RegDeleteValueA(policy_key, p->szPolicy);
}
if (r != ERROR_SUCCESS) {
ubprintf("SetLGP: RegSetValueEx / RegDeleteValue failed - error %lx", r);
}
RegCloseKey(policy_key);
policy_key = NULL;
// Apply policy
hr = pLGPO->lpVtbl->Save(pLGPO, TRUE, (p->bRestore)?FALSE:TRUE, &ext_guid, &snap_guid);
if (hr != S_OK) {
ubprintf("SetLGP: Unable to apply %s policy - error %lx", p->szPolicy, hr);
goto error;
} else {
if ((!p->bRestore) || (*(p->bExistingKey))) {
ubprintf("SetLGP: Successfully %s %s policy to 0x%08lX", (p->bRestore)?"restored":"set", p->szPolicy, val);
} else {
ubprintf("SetLGP: Successfully removed %s policy key", p->szPolicy);
}
}
RegCloseKey(path_key);
pLGPO->lpVtbl->Release(pLGPO);
return TRUE;
error:
if (path_key != NULL)
RegCloseKey(path_key);
if (pLGPO != NULL)
pLGPO->lpVtbl->Release(pLGPO);
CoUninitialize();
return FALSE;
}
BOOL SetLGP(BOOL bRestore, BOOL* bExistingKey, const char* szPath, const char* szPolicy, DWORD dwValue)
{
SetLGP_Params params = {bRestore, bExistingKey, szPath, szPolicy, dwValue};
DWORD r = FALSE;
HANDLE thread_id;
if (ReadSettingBool(SETTING_DISABLE_LGP)) {
ubprintf("LPG handling disabled, per settings");
return FALSE;
}
thread_id = CreateThread(NULL, 0, SetLGPThread, (LPVOID)&params, 0, NULL);
if (thread_id == NULL) {
ubprintf("SetLGP: Unable to start thread");
return FALSE;
}
if (WaitForSingleObject(thread_id, 5000) != WAIT_OBJECT_0) {
ubprintf("SetLGP: Killing stuck thread!");
TerminateThread(thread_id, 0);
CloseHandle(thread_id);
return FALSE;
}
if (!GetExitCodeThread(thread_id, &r))
return FALSE;
return (BOOL) r;
}
/*
* This call tries to evenly balance the affinities for an array of
* num_threads, according to the number of cores at our disposal...
*/
BOOL SetThreadAffinity(DWORD_PTR* thread_affinity, size_t num_threads)
{
size_t i, j, pc;
DWORD_PTR affinity, dummy;
memset(thread_affinity, 0, num_threads * sizeof(DWORD_PTR));
if (!GetProcessAffinityMask(GetCurrentProcess(), &affinity, &dummy))
return FALSE;
uuprintf("\r\nThread affinities:");
uuprintf(" avail:\t%s", printbitslz(affinity));
// If we don't have enough virtual cores to evenly spread our load forget it
pc = popcnt64(affinity);
if (pc < num_threads)
return FALSE;
// Spread the affinity as evenly as we can
thread_affinity[num_threads - 1] = affinity;
for (i = 0; i < num_threads - 1; i++) {
for (j = 0; j < pc / num_threads; j++) {
thread_affinity[i] |= affinity & (-1LL * affinity);
affinity ^= affinity & (-1LL * affinity);
}
uuprintf(" thr_%d:\t%s", i, printbitslz(thread_affinity[i]));
thread_affinity[num_threads - 1] ^= thread_affinity[i];
}
uuprintf(" thr_%d:\t%s", i, printbitslz(thread_affinity[i]));
return TRUE;
}
/*
* Returns true if:
* 1. The OS supports UAC, UAC is on, and the current process runs elevated, or
* 2. The OS doesn't support UAC or UAC is off, and the process is being run by a member of the admin group
*/
BOOL IsCurrentProcessElevated(void)
{
BOOL r = FALSE;
DWORD size;
HANDLE token = INVALID_HANDLE_VALUE;
TOKEN_ELEVATION te;
SID_IDENTIFIER_AUTHORITY auth = { SECURITY_NT_AUTHORITY };
PSID psid;
if (ReadRegistryKey32(REGKEY_HKLM, "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\EnableLUA") == 1) {
uprintf("Note: UAC is active");
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
uprintf("Could not get current process token: %s", WindowsErrorString());
goto out;
}
if (!GetTokenInformation(token, TokenElevation, &te, sizeof(te), &size)) {
uprintf("Could not get token information: %s", WindowsErrorString());
goto out;
}
r = (te.TokenIsElevated != 0);
} else {
uprintf("Note: UAC is either disabled or not available");
if (!AllocateAndInitializeSid(&auth, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psid))
goto out;
if (!CheckTokenMembership(NULL, psid, &r))
r = FALSE;
FreeSid(psid);
}
out:
safe_closehandle(token);
return r;
}
char* GetCurrentMUI(void)
{
static char mui_str[LOCALE_NAME_MAX_LENGTH];
wchar_t wmui_str[LOCALE_NAME_MAX_LENGTH];
if (LCIDToLocaleName(GetUserDefaultUILanguage(), wmui_str, LOCALE_NAME_MAX_LENGTH, 0) > 0) {
wchar_to_utf8_no_alloc(wmui_str, mui_str, LOCALE_NAME_MAX_LENGTH);
} else {
static_strcpy(mui_str, "en-US");
}
return mui_str;
}
/*
* From: https://stackoverflow.com/a/40390858/1069307
*/
BOOL SetPrivilege(HANDLE hToken, LPCWSTR pwzPrivilegeName, BOOL bEnable)
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValue(NULL, pwzPrivilegeName, &luid)) {
uprintf("Could not lookup '%S' privilege: %s", pwzPrivilegeName, WindowsErrorString());
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
uprintf("Could not %s '%S' privilege: %s",
bEnable ? "enable" : "disable", pwzPrivilegeName, WindowsErrorString());
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
uprintf("Error assigning privileges: %s", WindowsErrorString());
return FALSE;
}
return TRUE;
}
/*
* Mount an offline registry hive located at <pszHivePath> into <key>\<pszHiveName>.
* <key> should be HKEY_LOCAL_MACHINE or HKEY_USERS.
*/
BOOL MountRegistryHive(const HKEY key, const char* pszHiveName, const char* pszHivePath)
{
LSTATUS status;
HANDLE token = INVALID_HANDLE_VALUE;
assert((key == HKEY_LOCAL_MACHINE) || (key == HKEY_USERS));
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) {
uprintf("Could not get current process token: %s", WindowsErrorString());
return FALSE;
}
// Ignore errors on those in case we can proceed without...
SetPrivilege(token, SE_RESTORE_NAME, TRUE);
SetPrivilege(token, SE_BACKUP_NAME, TRUE);
status = RegLoadKeyA(key, pszHiveName, pszHivePath);
if (status != ERROR_SUCCESS) {
SetLastError(status);
uprintf("Could not mount offline registry hive '%s': %s", pszHivePath, WindowsErrorString());
} else
uprintf("Mounted offline registry hive '%s' to '%s\\%s'",
pszHivePath, (key == HKEY_LOCAL_MACHINE) ? "HKLM" : "HKCU", pszHiveName);
safe_closehandle(token);
return (status == ERROR_SUCCESS);
}
/*
* Unmount an offline registry hive.
* <key> should be HKEY_LOCAL_MACHINE or HKEY_USERS.
*/
BOOL UnmountRegistryHive(const HKEY key, const char* pszHiveName)
{
LSTATUS status;
assert((key == HKEY_LOCAL_MACHINE) || (key == HKEY_USERS));
status = RegUnLoadKeyA(key, pszHiveName);
if (status != ERROR_SUCCESS) {
SetLastError(status);
uprintf("Could not unmount offline registry hive: %s", WindowsErrorString());
} else
uprintf("Unmounted offline registry hive '%s\\%s'",
(key == HKEY_LOCAL_MACHINE) ? "HKLM" : "HKCU", pszHiveName);
return (status == ERROR_SUCCESS);
}