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

Linux/FreeBSD: Prevent mounting volumes on system directories and PATH (CVE-2025-23021, reported by SivertPL @__tfr)

Added security checks to prevent mounting VeraCrypt volumes on system directories (like /usr/bin) or directories in the user's PATH, which could theoretically allow execution of malicious binaries instead of legitimate system binaries.

Key changes:
- Block mounting on protected system directories (/usr, /bin, /lib, etc.)
  This restriction cannot be overridden
- Block mounting on directories present in user's PATH environment variable
  This can be overridden with --allow-insecure-mount flag
- Add visual warnings (red border, "[INSECURE MODE]") when mounting on PATH directories is allowed
- Handle symlinks properly when checking paths
- Add new error messages for blocked mount points

To override PATH-based restrictions only (system directories remain protected):
veracrypt --allow-insecure-mount [options] volume mountpoint

Security Impact: Low to Medium
The attack requires either:
- User explicitly choosing a system directory as mount point instead of using VeraCrypt's default mount points
- Or attacker having both filesystem access to modify favorites configuration AND knowledge of the volume password
Default mount points are not affected by this vulnerability.

Security: CVE-2025-23021
This commit is contained in:
Mounir IDRASSI
2025-01-11 23:22:40 +01:00
parent 2cca2e1daf
commit 078d1410dd
59 changed files with 370 additions and 6 deletions

View File

@@ -22,6 +22,9 @@ namespace VeraCrypt
: DeviceChangeInProgress (false)
#if defined(TC_LINUX ) || defined (TC_FREEBSD)
, UseDummySudoPassword (false)
#endif
#if defined(TC_UNIX)
,AllowInsecureMount (false)
#endif
{
}

View File

@@ -80,6 +80,16 @@ namespace VeraCrypt
virtual void ForceUseDummySudoPassword (bool useDummySudoPassword) { UseDummySudoPassword = useDummySudoPassword;}
virtual bool GetUseDummySudoPassword () const { return UseDummySudoPassword;}
#if defined(TC_UNIX)
virtual bool IsProtectedSystemDirectory (const DirectoryPath &directory) const = 0;
virtual bool IsDirectoryOnUserPath(const DirectoryPath &directory) const = 0;
virtual void SetAllowInsecureMount (bool allowInsecureMount) { AllowInsecureMount = allowInsecureMount; }
virtual bool GetAllowInsecureMount () const { return AllowInsecureMount; }
#endif
virtual void SetUserEnvPATH (const string &path) { UserEnvPATH = path; }
virtual string GetUserEnvPATH () const { return UserEnvPATH; }
Event VolumeDismountedEvent;
Event VolumeMountedEvent;
Event WarningEvent;
@@ -89,8 +99,13 @@ namespace VeraCrypt
bool DeviceChangeInProgress;
FilePath ApplicationExecutablePath;
string UserEnvPATH;
bool UseDummySudoPassword;
#if defined(TC_UNIX)
bool AllowInsecureMount;
#endif
private:
CoreBase (const CoreBase &);
CoreBase &operator= (const CoreBase &);

View File

@@ -99,6 +99,11 @@ namespace VeraCrypt
{
shared_ptr <CoreServiceRequest> request = Serializable::DeserializeNew <CoreServiceRequest> (inputStream);
// Update Core properties based on the received request
Core->SetUserEnvPATH (request->UserEnvPATH);
Core->ForceUseDummySudoPassword(request->UseDummySudoPassword);
Core->SetAllowInsecureMount(request->AllowInsecureMount);
try
{
// ExitRequest
@@ -283,12 +288,17 @@ namespace VeraCrypt
static Mutex mutex;
ScopeLock lock (mutex);
// Copy Core properties to the request so that they can be transferred to the elevated process
request.ApplicationExecutablePath = Core->GetApplicationExecutablePath();
request.UserEnvPATH = Core->GetUserEnvPATH();
request.UseDummySudoPassword = Core->GetUseDummySudoPassword();
request.AllowInsecureMount = Core->GetAllowInsecureMount();
if (request.RequiresElevation())
{
request.ElevateUserPrivileges = true;
request.FastElevation = !ElevatedServiceAvailable;
request.ApplicationExecutablePath = Core->GetApplicationExecutablePath();
while (!ElevatedServiceAvailable)
{
// Test if the user has an active "sudo" session.

View File

@@ -23,6 +23,9 @@ namespace VeraCrypt
ApplicationExecutablePath = sr.DeserializeWString ("ApplicationExecutablePath");
sr.Deserialize ("ElevateUserPrivileges", ElevateUserPrivileges);
sr.Deserialize ("FastElevation", FastElevation);
sr.Deserialize ("UserEnvPATH", UserEnvPATH);
sr.Deserialize ("UseDummySudoPassword", UseDummySudoPassword);
sr.Deserialize ("AllowInsecureMount", AllowInsecureMount);
}
void CoreServiceRequest::Serialize (shared_ptr <Stream> stream) const
@@ -33,6 +36,9 @@ namespace VeraCrypt
sr.Serialize ("ApplicationExecutablePath", wstring (ApplicationExecutablePath));
sr.Serialize ("ElevateUserPrivileges", ElevateUserPrivileges);
sr.Serialize ("FastElevation", FastElevation);
sr.Serialize ("UserEnvPATH", UserEnvPATH);
sr.Serialize ("UseDummySudoPassword", UseDummySudoPassword);
sr.Serialize ("AllowInsecureMount", AllowInsecureMount);
}
// CheckFilesystemRequest

View File

@@ -20,7 +20,7 @@ namespace VeraCrypt
{
struct CoreServiceRequest : public Serializable
{
CoreServiceRequest () : ElevateUserPrivileges (false), FastElevation (false) { }
CoreServiceRequest () : ElevateUserPrivileges (false), FastElevation (false), UseDummySudoPassword (false), AllowInsecureMount (false) { }
TC_SERIALIZABLE (CoreServiceRequest);
virtual bool RequiresElevation () const { return false; }
@@ -29,6 +29,9 @@ namespace VeraCrypt
FilePath ApplicationExecutablePath;
bool ElevateUserPrivileges;
bool FastElevation;
string UserEnvPATH;
bool UseDummySudoPassword;
bool AllowInsecureMount;
};
struct CheckFilesystemRequest : CoreServiceRequest

View File

@@ -596,6 +596,17 @@ namespace VeraCrypt
if (IsVolumeMounted (*options.Path))
throw VolumeAlreadyMounted (SRC_POS);
if (options.MountPoint && !options.MountPoint->IsEmpty())
{
// Reject if the mount point is a system directory
if (IsProtectedSystemDirectory(*options.MountPoint))
throw MountPointBlocked (SRC_POS);
// Reject if the mount point is in the user's PATH and the user has not explicitly allowed insecure mount points
if (!GetAllowInsecureMount() && IsDirectoryOnUserPath(*options.MountPoint))
throw MountPointNotAllowed (SRC_POS);
}
Cipher::EnableHwSupport (!options.NoHardwareCrypto);
shared_ptr <Volume> volume;
@@ -828,4 +839,100 @@ namespace VeraCrypt
s << GetDefaultMountPointPrefix() << slotNumber;
return s.str();
}
bool CoreUnix::IsProtectedSystemDirectory (const DirectoryPath &directory) const
{
static const char* systemDirs[] = {
"/usr",
"/bin",
"/sbin",
"/lib",
#ifdef TC_LINUX
"/lib32",
"/lib64",
"/libx32",
#endif
"/etc",
"/boot",
"/root",
"/proc",
"/sys",
"/dev",
NULL
};
// Resolve any symlinks in the path
string path(directory);
char* resolvedPathCStr = realpath(path.c_str(), NULL);
if (resolvedPathCStr)
{
path = resolvedPathCStr;
free(resolvedPathCStr); // Free the allocated memory
}
// reject of the path is the root directory "/"
if (path == "/")
return true;
// Check if resolved path matches any system directory
for (int i = 0; systemDirs[i] != NULL; ++i)
{
if (path == systemDirs[i] || path.find(string(systemDirs[i]) + "/") == 0)
return true;
}
return false;
}
bool CoreUnix::IsDirectoryOnUserPath(const DirectoryPath &directory) const
{
// Obtain the PATH environment variable
const char* pathEnv = UserEnvPATH.c_str();
if (!pathEnv[0])
return false;
// Resolve the given directory
string dirPath(directory);
char* resolvedDir = realpath(dirPath.c_str(), NULL);
if (resolvedDir)
{
dirPath = resolvedDir;
free(resolvedDir);
}
// Split PATH and compare each entry
stringstream ss(pathEnv);
string token;
while (getline(ss, token, ':'))
{
// remove any trailing slashes from the token
while (!token.empty() && token.back() == '/')
token.pop_back();
if (token.empty())
continue;
// check if the directory is the same as the entry or a subdirectory
if (dirPath == token || dirPath.find(token + "/") == 0)
return true;
// handle the case where the PATH entry is a symlink
char* resolvedEntry = realpath(token.c_str(), NULL);
if (!resolvedEntry)
continue; // skip to the next entry since the path does not exist
string entryPath(resolvedEntry);
free(resolvedEntry);
// remove any trailing slashes from the token
while (!entryPath.empty() && entryPath.back() == '/')
entryPath.pop_back();
// perform check again if the resolved path is different from the original (symlink)
if (dirPath == entryPath || dirPath.find(entryPath + "/") == 0)
return true;
}
return false;
}
}

View File

@@ -48,6 +48,8 @@ namespace VeraCrypt
virtual void SetFileOwner (const FilesystemPath &path, const UserId &owner) const;
virtual DirectoryPath SlotNumberToMountPoint (VolumeSlotNumber slotNumber) const;
virtual void WipePasswordCache () const { throw NotApplicable (SRC_POS); }
virtual bool IsProtectedSystemDirectory (const DirectoryPath &directory) const;
virtual bool IsDirectoryOnUserPath(const DirectoryPath &directory) const;
protected:
virtual DevicePath AttachFileToLoopDevice (const FilePath &filePath, bool readOnly) const { throw NotApplicable (SRC_POS); }