1
0
mirror of https://github.com/veracrypt/VeraCrypt.git synced 2025-11-11 11:08:02 -06:00

Linux/FreeBSD: Add absolute paths for system binaries to prevent path hijacking (CVE-2024-54187, collaboration with SivertPL @__tfr)

This commit fixes a critical security vulnerability where VeraCrypt could be tricked into executing malicious binaries with elevated privileges. The vulnerability has two severe implications:

1. When sudo's secure_path option is disabled, attackers could execute malicious binaries with root privileges by placing them in user-writable PATH directories (e.g., making "sudo mount" execute a malicious mount binary)

2. By placing a malicious sudo binary in PATH, attackers could intercept and steal the user's password when VeraCrypt prompts for sudo authentication

The vulnerability allowed attackers to place malicious binaries in user-writable directories that appear in PATH before system directories, potentially leading to privilege escalation and credential theft.

Key changes:
- Implement FindSystemBinary() to locate executables in secure system paths
- Replace all relative binary paths with absolute paths for system commands
- Add security checks for executable permissions
- Update process execution to use absolute paths for:
  * sudo
  * mount
  * fsck
  * terminal emulators
  * file managers
  * system utilities (hdiutil, mdconfig, vnconfig, lofiadm)

The fix ensures all system binaries are called using their absolute paths from secure system directories, preventing both privilege escalation through PATH manipulation and password theft through sudo hijacking.

Security: CVE-2024-54187
This commit is contained in:
Mounir IDRASSI
2025-01-11 17:26:03 +01:00
parent 1b35abb191
commit 2cca2e1daf
9 changed files with 226 additions and 99 deletions

View File

@@ -300,7 +300,14 @@ namespace VeraCrypt
// We are using -n to avoid prompting the user for a password. // We are using -n to avoid prompting the user for a password.
// We are redirecting stderr to stdout and discarding both to avoid any output. // We are redirecting stderr to stdout and discarding both to avoid any output.
// This approach also works on newer macOS versions (12.0 and later). // This approach also works on newer macOS versions (12.0 and later).
FILE* pipe = popen("sudo -n -l > /dev/null 2>&1", "r"); // redirect stderr to stdout and discard both. std::string errorMsg;
string sudoAbsolutePath = Process::FindSystemBinary("sudo", errorMsg);
if (sudoAbsolutePath.empty())
throw SystemException(SRC_POS, errorMsg);
std::string popenCommand = sudoAbsolutePath + " -n -l > /dev/null 2>&1"; // We redirect stderr to stdout (2>&1) to be able to catch the result of the command
FILE* pipe = popen(popenCommand.c_str(), "r");
if (pipe) if (pipe)
{ {
// We only care about the exit code // We only care about the exit code
@@ -396,15 +403,26 @@ namespace VeraCrypt
{ {
try try
{ {
// Throw exception if sudo is not found in secure locations
std::string errorMsg;
string sudoPath = Process::FindSystemBinary("sudo", errorMsg);
if (sudoPath.empty())
throw SystemException(SRC_POS, errorMsg);
string appPath = request.ApplicationExecutablePath;
// if appPath is empty or not absolute, use FindSystemBinary to get the full path of veracrpyt executable
if (appPath.empty() || appPath[0] != '/')
{
appPath = Process::FindSystemBinary("veracrypt", errorMsg);
if (appPath.empty())
throw SystemException(SRC_POS, errorMsg);
}
throw_sys_if (dup2 (inPipe->GetReadFD(), STDIN_FILENO) == -1); throw_sys_if (dup2 (inPipe->GetReadFD(), STDIN_FILENO) == -1);
throw_sys_if (dup2 (outPipe->GetWriteFD(), STDOUT_FILENO) == -1); throw_sys_if (dup2 (outPipe->GetWriteFD(), STDOUT_FILENO) == -1);
throw_sys_if (dup2 (errPipe.GetWriteFD(), STDERR_FILENO) == -1); throw_sys_if (dup2 (errPipe.GetWriteFD(), STDERR_FILENO) == -1);
string appPath = request.ApplicationExecutablePath; const char *args[] = { sudoPath.c_str(), "-S", "-p", "", appPath.c_str(), TC_CORE_SERVICE_CMDLINE_OPTION, nullptr };
if (appPath.empty())
appPath = "veracrypt";
const char *args[] = { "sudo", "-S", "-p", "", appPath.c_str(), TC_CORE_SERVICE_CMDLINE_OPTION, nullptr };
execvp (args[0], ((char* const*) args)); execvp (args[0], ((char* const*) args));
throw SystemException (SRC_POS, args[0]); throw SystemException (SRC_POS, args[0]);
} }

View File

@@ -29,6 +29,41 @@ namespace VeraCrypt
static bool SamePath (const string& path1, const string& path2); static bool SamePath (const string& path1, const string& path2);
#endif #endif
// Struct to hold terminal emulator information
struct TerminalInfo {
const char* name;
const char** args;
const char** dependency_path;
};
// Popular terminal emulators data and arguments
static const char* xterm_args[] = {"-T", "fsck", "-e", NULL};
static const char* gnome_args[] = {"--title", "fsck", "--", "sh", "-c", NULL};
static const char* gnome_deps[] = {"dbus-launch", NULL};
static const char* konsole_args[] = {"--hold", "-p", "tabtitle=fsck", "-e", "sh", "-c", NULL};
static const char* xfce4_args[] = {"--title=fsck", "-x", "sh", "-c", NULL};
static const char* mate_args[] = {"--title", "fsck", "--", "sh", "-c", NULL};
static const char* lxterminal_args[] = {"--title=fsck", "-e", "sh", "-c", NULL};
static const char* terminator_args[] = {"-T", "fsck", "-x", "sh", "-c", NULL};
static const char* urxvt_args[] = {"-title", "fsck", "-e", "sh", "-c", NULL};
static const char* st_args[] = {"-t", "fsck", "-e", "sh", "-c", NULL};
// List of popular terminal emulators
static const TerminalInfo TERMINALS[] = {
{"xterm", xterm_args, NULL},
{"gnome-terminal", gnome_args, gnome_deps},
{"konsole", konsole_args, NULL},
{"xfce4-terminal", xfce4_args, NULL},
{"mate-terminal", mate_args, NULL},
{"lxterminal", lxterminal_args, NULL},
{"terminator", terminator_args, NULL},
{"urxvt", urxvt_args, NULL},
{"st", st_args, NULL},
{NULL, NULL, NULL}
};
CoreUnix::CoreUnix () CoreUnix::CoreUnix ()
{ {
signal (SIGPIPE, SIG_IGN); signal (SIGPIPE, SIG_IGN);
@@ -47,14 +82,16 @@ namespace VeraCrypt
if (!mountedVolume->MountPoint.IsEmpty()) if (!mountedVolume->MountPoint.IsEmpty())
DismountFilesystem (mountedVolume->MountPoint, false); DismountFilesystem (mountedVolume->MountPoint, false);
// Find system fsck first
std::string errorMsg;
std::string fsckPath = Process::FindSystemBinary("fsck", errorMsg);
if (fsckPath.empty()) {
throw SystemException(SRC_POS, errorMsg);
}
list <string> args; list <string> args;
args.push_back ("-T"); string xargs = fsckPath + " "; // Use absolute fsck path
args.push_back ("fsck");
args.push_back ("-e");
string xargs = "fsck ";
#ifdef TC_LINUX #ifdef TC_LINUX
if (!repair) if (!repair)
@@ -64,49 +101,48 @@ namespace VeraCrypt
#endif #endif
xargs += string (mountedVolume->VirtualDevice) + "; echo '[Done]'; read W"; xargs += string (mountedVolume->VirtualDevice) + "; echo '[Done]'; read W";
args.push_back (xargs); // Try each terminal
for (const TerminalInfo* term = TERMINALS; term->name != NULL; ++term) {
errno = 0;
std::string termPath = Process::FindSystemBinary(term->name, errorMsg);
if (termPath.length() > 0) {
// check dependencies
if (term->dependency_path) {
bool depFound = true;
for (const char** dep = term->dependency_path; *dep != NULL; ++dep) {
string depPath = Process::FindSystemBinary(*dep, errorMsg);
if (depPath.empty()) {
depFound = false;
break;
}
}
try if (!depFound) {
{ continue; // dependency not found, skip
Process::Execute ("xterm", args, 1000); }
} catch (TimeOut&) { } }
#ifdef TC_LINUX
catch (SystemException&) // Build args
{ std::list<std::string> args;
// xterm not available. Try with KDE konsole if it exists for (const char** arg = term->args; *arg != NULL; ++arg) {
struct stat sb; args.push_back(*arg);
if (stat("/usr/bin/konsole", &sb) == 0) }
{ args.push_back(xargs);
args.clear ();
args.push_back ("-p"); try {
args.push_back ("tabtitle=fsck"); Process::Execute (termPath, args, 1000);
args.push_back ("-e"); return;
args.push_back ("sh"); }
args.push_back ("-c"); catch (TimeOut&) {
args.push_back (xargs); return;
try }
{ catch (SystemException&) {
Process::Execute ("konsole", args, 1000); // Continue to next terminal
} catch (TimeOut&) { } }
} }
else if (stat("/usr/bin/gnome-terminal", &sb) == 0 && stat("/usr/bin/dbus-launch", &sb) == 0)
{
args.clear ();
args.push_back ("--title");
args.push_back ("fsck");
args.push_back ("--");
args.push_back ("sh");
args.push_back ("-c");
args.push_back (xargs);
try
{
Process::Execute ("gnome-terminal", args, 1000);
} catch (TimeOut&) { }
}
else
throw TerminalNotFound();
} }
#endif
throw TerminalNotFound();
} }
void CoreUnix::DismountFilesystem (const DirectoryPath &mountPoint, bool force) const void CoreUnix::DismountFilesystem (const DirectoryPath &mountPoint, bool force) const

View File

@@ -46,7 +46,7 @@ namespace VeraCrypt
args.push_back ("-f"); args.push_back ("-f");
args.push_back (filePath); args.push_back (filePath);
string dev = StringConverter::Trim (Process::Execute ("mdconfig", args)); string dev = StringConverter::Trim (Process::Execute ("/sbin/mdconfig", args));
if (dev.find ("/") == string::npos) if (dev.find ("/") == string::npos)
dev = string ("/dev/") + dev; dev = string ("/dev/") + dev;
@@ -65,7 +65,7 @@ namespace VeraCrypt
{ {
try try
{ {
Process::Execute ("mdconfig", args); Process::Execute ("/sbin/mdconfig", args);
break; break;
} }
catch (ExecutedProcessFailed&) catch (ExecutedProcessFailed&)

View File

@@ -47,7 +47,7 @@ namespace VeraCrypt
try try
{ {
Process::Execute ("hdiutil", args); Process::Execute ("/usr/bin/hdiutil", args);
} }
catch (ExecutedProcessFailed &e) catch (ExecutedProcessFailed &e)
{ {
@@ -84,7 +84,7 @@ namespace VeraCrypt
{ {
try try
{ {
Process::Execute ("umount", args); Process::Execute ("/usr/bin/umount", args);
break; break;
} }
catch (ExecutedProcessFailed&) catch (ExecutedProcessFailed&)
@@ -114,7 +114,7 @@ namespace VeraCrypt
else else
args.push_back ("/System/Applications/Utilities/Disk Utility.app"); args.push_back ("/System/Applications/Utilities/Disk Utility.app");
Process::Execute ("open", args); Process::Execute ("/usr/bin/open", args);
} }
void CoreMacOSX::MountAuxVolumeImage (const DirectoryPath &auxMountPoint, const MountOptions &options) const void CoreMacOSX::MountAuxVolumeImage (const DirectoryPath &auxMountPoint, const MountOptions &options) const
@@ -190,7 +190,7 @@ namespace VeraCrypt
{ {
try try
{ {
xml = Process::Execute ("hdiutil", args); xml = Process::Execute ("/usr/bin/hdiutil", args);
break; break;
} }
catch (ExecutedProcessFailed &e) catch (ExecutedProcessFailed &e)
@@ -233,7 +233,7 @@ namespace VeraCrypt
args.push_back (volImage); args.push_back (volImage);
args.push_back ("-force"); args.push_back ("-force");
Process::Execute ("hdiutil", args); Process::Execute ("/usr/bin/hdiutil", args);
} }
catch (ExecutedProcessFailed&) { } catch (ExecutedProcessFailed&) { }
throw; throw;

View File

@@ -75,7 +75,7 @@ namespace VeraCrypt
args.push_back (filePath); args.push_back (filePath);
Process::Execute ("vnconfig", args); Process::Execute ("/sbin/vnconfig", args);
return "/dev/" + freePath.str() + "c"; return "/dev/" + freePath.str() + "c";
} }
@@ -90,7 +90,7 @@ namespace VeraCrypt
{ {
try try
{ {
Process::Execute ("vnconfig", args); Process::Execute ("/sbin/vnconfig", args);
break; break;
} }
catch (ExecutedProcessFailed&) catch (ExecutedProcessFailed&)

View File

@@ -35,7 +35,7 @@ namespace VeraCrypt
args.push_back ("-a"); args.push_back ("-a");
args.push_back (filePath); args.push_back (filePath);
return StringConverter::Trim (Process::Execute ("lofiadm", args)); return StringConverter::Trim (Process::Execute ("/usr/sbin/lofiadm", args));
} }
void CoreSolaris::DetachLoopDevice (const DevicePath &devicePath) const void CoreSolaris::DetachLoopDevice (const DevicePath &devicePath) const
@@ -48,7 +48,7 @@ namespace VeraCrypt
{ {
try try
{ {
Process::Execute ("lofiadm", args); Process::Execute ("/usr/sbin/lofiadm", args);
break; break;
} }
catch (ExecutedProcessFailed&) catch (ExecutedProcessFailed&)

View File

@@ -872,11 +872,30 @@ namespace VeraCrypt
} }
#if !defined(TC_WINDOWS) && !defined(TC_MACOSX) #if !defined(TC_WINDOWS) && !defined(TC_MACOSX)
// Function to check if a given executable exists and is executable // Define file manager structures with their required parameters
static bool IsExecutable(const string& exe) { struct FileManager {
return wxFileName::IsFileExecutable("/usr/bin/" + exe) || const char* name;
wxFileName::IsFileExecutable("/usr/local/bin/" + exe); const char* const* baseArgs;
} size_t baseArgsCount;
};
// Array of supported file managers with their parameters
static const char* const gioArgs[] = {"open"};
static const char* const kioclient5Args[] = {"exec"};
static const char* const kfmclientArgs[] = {"openURL"};
static const char* const exoOpenArgs[] = {"--launch", "FileManager"};
const FileManager fileManagers[] = {
{"gio", gioArgs, 1},
{"kioclient5", kioclient5Args, 1},
{"kfmclient", kfmclientArgs, 1},
{"exo-open", exoOpenArgs, 2},
{"nautilus", NULL, 0},
{"dolphin", NULL, 0},
{"caja", NULL, 0},
{"thunar", NULL, 0},
{"pcmanfm", NULL, 0}
};
#endif #endif
void UserInterface::OpenExplorerWindow (const DirectoryPath &path) void UserInterface::OpenExplorerWindow (const DirectoryPath &path)
@@ -898,17 +917,21 @@ static bool IsExecutable(const string& exe) {
args.push_back (string (path)); args.push_back (string (path));
try try
{ {
Process::Execute ("open", args); Process::Execute ("/usr/bin/open", args);
} }
catch (exception &e) { ShowError (e); } catch (exception &e) { ShowError (e); }
#else #else
string directoryPath = string(path); string directoryPath = string(path);
// Primary attempt: Use xdg-open // Primary attempt: Use xdg-open
if (IsExecutable("xdg-open")) { string errorMsg;
try { string binPath = Process::FindSystemBinary("xdg-open", errorMsg);
if (!binPath.empty())
{
try
{
args.push_back(directoryPath); args.push_back(directoryPath);
Process::Execute("xdg-open", args, 2000); Process::Execute(binPath, args, 2000);
return; return;
} }
catch (TimeOut&) { } catch (TimeOut&) { }
@@ -916,36 +939,23 @@ static bool IsExecutable(const string& exe) {
} }
// Fallback attempts: Try known file managers // Fallback attempts: Try known file managers
const char* fallbackFileManagers[] = { "gio", "kioclient5", "kfmclient", "exo-open", "nautilus", "dolphin", "caja", "thunar", "pcmanfm" }; const size_t numFileManagers = sizeof(fileManagers) / sizeof(fileManagers[0]);
const size_t numFileManagers = sizeof(fallbackFileManagers) / sizeof(fallbackFileManagers[0]);
for (size_t i = 0; i < numFileManagers; ++i) { for (size_t i = 0; i < numFileManagers; ++i) {
const char* fm = fallbackFileManagers[i]; const FileManager& fm = fileManagers[i];
if (IsExecutable(fm)) { string fmPath = Process::FindSystemBinary(fm.name, errorMsg);
if (!fmPath.empty()) {
args.clear(); args.clear();
if (strcmp(fm, "gio") == 0) {
args.push_back("open"); // Add base arguments first
args.push_back(directoryPath); for (size_t j = 0; j < fm.baseArgsCount; ++j) {
} args.push_back(fm.baseArgs[j]);
else if (strcmp(fm, "kioclient5") == 0) {
args.push_back("exec");
args.push_back(directoryPath);
}
else if (strcmp(fm, "kfmclient") == 0) {
args.push_back("openURL");
args.push_back(directoryPath);
}
else if (strcmp(fm, "exo-open") == 0) {
args.push_back("--launch");
args.push_back("FileManager");
args.push_back(directoryPath);
}
else {
args.push_back(directoryPath);
} }
// Add path argument
args.push_back(directoryPath);
try { try {
Process::Execute(fm, args, 2000); Process::Execute(fmPath, args, 2000);
return; // Success return; // Success
} }
catch (TimeOut&) { } catch (TimeOut&) { }
@@ -954,7 +964,7 @@ static bool IsExecutable(const string& exe) {
} }
ShowWarning(wxT("Unable to find a file manager to open the mounted volume.\n" ShowWarning(wxT("Unable to find a file manager to open the mounted volume.\n"
"Please install xdg-utils or set a default file manager.")); "Please install xdg-utils or set a default file manager."));
#endif #endif
} }

View File

@@ -27,12 +27,73 @@
namespace VeraCrypt namespace VeraCrypt
{ {
string Process::Execute (const string &processName, const list <string> &arguments, int timeOut, ProcessExecFunctor *execFunctor, const Buffer *inputData)
bool Process::IsExecutable(const std::string& path) {
struct stat sb;
if (stat(path.c_str(), &sb) == 0) {
return S_ISREG(sb.st_mode) && (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH));
}
return false;
}
// Find executable in system paths
std::string Process::FindSystemBinary(const char* name, std::string& errorMsg) {
if (!name) {
errno = EINVAL; // Invalid argument
errorMsg = "Invalid input: name or paths is NULL";
return "";
}
// Default system directories to search for executables
#ifdef TC_MACOSX
const char* defaultDirs[] = {"/usr/local/bin", "/usr/bin", "/bin", "/user/sbin", "/sbin"};
#elseif TC_FREEBSD
const char* defaultDirs[] = {"/sbin", "/bin", "/usr/sbin", "/usr/bin", "/usr/local/sbin", "/usr/local/bin"};
#elseif TC_OPENBSD
const char* defaultDirs[] = {"/sbin", "/bin", "/usr/sbin", "/usr/bin", "/usr/X11R6/bin", "/usr/local/sbin", "/usr/local/bin"};
#else
const char* defaultDirs[] = {"/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"};
#endif
const size_t defaultDirCount = sizeof(defaultDirs) / sizeof(defaultDirs[0]);
std::string currentPath(name);
// If path doesn't start with '/', prepend default directories
if (currentPath[0] != '/') {
for (size_t i = 0; i < defaultDirCount; ++i) {
std::string combinedPath = std::string(defaultDirs[i]) + "/" + currentPath;
if (IsExecutable(combinedPath)) {
return combinedPath;
}
}
} else if (IsExecutable(currentPath)) {
return currentPath;
}
// Prepare error message
errno = ENOENT; // No such file or directory
errorMsg = std::string(name) + " not found in system directories";
return "";
}
string Process::Execute (const string &processNameArg, const list <string> &arguments, int timeOut, ProcessExecFunctor *execFunctor, const Buffer *inputData)
{ {
char *args[32]; char *args[32];
if (array_capacity (args) <= (arguments.size() + 1)) if (array_capacity (args) <= (arguments.size() + 1))
throw ParameterTooLarge (SRC_POS); throw ParameterTooLarge (SRC_POS);
// if execFunctor is null and processName is not absolute path, find it in system paths
string processName;
if (!execFunctor && (processNameArg[0] != '/'))
{
std::string errorMsg;
processName = FindSystemBinary(processNameArg.c_str(), errorMsg);
if (processName.empty())
throw SystemException(SRC_POS, errorMsg);
}
else
processName = processNameArg;
#if 0 #if 0
stringstream dbg; stringstream dbg;
dbg << "exec " << processName; dbg << "exec " << processName;

View File

@@ -31,6 +31,8 @@ namespace VeraCrypt
Process (); Process ();
virtual ~Process (); virtual ~Process ();
static bool IsExecutable(const std::string& path);
static std::string FindSystemBinary(const char* name, std::string& errorMsg);
static string Execute (const string &processName, const list <string> &arguments, int timeOut = -1, ProcessExecFunctor *execFunctor = nullptr, const Buffer *inputData = nullptr); static string Execute (const string &processName, const list <string> &arguments, int timeOut = -1, ProcessExecFunctor *execFunctor = nullptr, const Buffer *inputData = nullptr);
protected: protected: