/* Derived from source code of TrueCrypt 7.1a, which is Copyright (c) 2008-2012 TrueCrypt Developers Association and which is governed by the TrueCrypt License 3.0. Modifications and additions to the original source code (contained in this file) and all other portions of this file are Copyright (c) 2013-2016 IDRIX and are governed by the Apache License 2.0 the full text of which is contained in the file License.txt included in VeraCrypt binary and source code distribution packages. */ #include #include #include #include #include #include #include #include #include "CoreLinux.h" #include "Platform/SystemInfo.h" #include "Platform/TextReader.h" #include "Volume/EncryptionModeXTS.h" #include "Driver/Fuse/FuseService.h" #include "Core/Unix/CoreServiceProxy.h" namespace VeraCrypt { CoreLinux::CoreLinux () { } CoreLinux::~CoreLinux () { } DevicePath CoreLinux::AttachFileToLoopDevice (const FilePath &filePath, bool readOnly) const { list loopPaths; loopPaths.push_back ("/dev/loop"); loopPaths.push_back ("/dev/loop/"); loopPaths.push_back ("/dev/.static/dev/loop"); // On Fedora 23,"losetup -f" must be called first to create a default loop device list args; args.push_back ("-f"); try { Process::Execute ("losetup", args); } catch (...) { } for (int devIndex = 0; devIndex < 256; devIndex++) { string loopDev; foreach (const string &loopPath, loopPaths) { loopDev = loopPath + StringConverter::ToSingle (devIndex); if (FilesystemPath (loopDev).IsBlockDevice()) break; } if (loopDev.empty()) continue; list args; list ::iterator readOnlyArg; if (readOnly) { args.push_back ("-r"); readOnlyArg = --args.end(); } args.push_back ("--"); args.push_back (loopDev); args.push_back (filePath); try { Process::Execute ("losetup", args); return loopDev; } catch (ExecutedProcessFailed&) { if (readOnly) { try { args.erase (readOnlyArg); Process::Execute ("losetup", args); return loopDev; } catch (ExecutedProcessFailed&) { } } } } throw LoopDeviceSetupFailed (SRC_POS, wstring (filePath)); } void CoreLinux::DetachLoopDevice (const DevicePath &devicePath) const { list args; args.push_back ("-d"); args.push_back (devicePath); for (int t = 0; true; t++) { try { Process::Execute ("losetup", args); break; } catch (ExecutedProcessFailed&) { if (t > 5) throw; Thread::Sleep (200); } } } void CoreLinux::DismountNativeVolume (shared_ptr mountedVolume) const { string devPath = mountedVolume->VirtualDevice; if (devPath.find ("/dev/mapper/veracrypt") != 0) throw NotApplicable (SRC_POS); size_t devCount = 0; while (FilesystemPath (devPath).IsBlockDevice()) { list dmsetupArgs; dmsetupArgs.push_back ("remove"); dmsetupArgs.push_back (StringConverter::Split (devPath, "/").back()); for (int t = 0; true; t++) { try { Process::Execute ("dmsetup", dmsetupArgs); break; } catch (...) { if (t > 20) throw; Thread::Sleep (100); } } for (int t = 0; FilesystemPath (devPath).IsBlockDevice() && t < 20; t++) { Thread::Sleep (100); } devPath = string (mountedVolume->VirtualDevice) + "_" + StringConverter::ToSingle (devCount++); } } HostDeviceList CoreLinux::GetHostDevices (bool pathListOnly) const { HostDeviceList devices; TextReader tr ("/proc/partitions"); string line; while (tr.ReadLine (line)) { vector fields = StringConverter::Split (line); if (fields.size() != 4 || fields[3].find ("loop") == 0 // skip loop devices || fields[3].find ("cloop") == 0 || fields[3].find ("ram") == 0 // skip RAM devices || fields[3].find ("dm-") == 0 // skip device mapper devices || fields[2] == "1" // skip extended partitions ) continue; try { StringConverter::ToUInt32 (fields[0]); } catch (...) { continue; } try { make_shared_auto (HostDevice, hostDevice); hostDevice->Path = string (fields[3].find ("/dev/") == string::npos ? "/dev/" : "") + fields[3]; if (!pathListOnly) { hostDevice->Size = StringConverter::ToUInt64 (fields[2]) * 1024; hostDevice->MountPoint = GetDeviceMountPoint (hostDevice->Path); hostDevice->SystemNumber = 0; } try { StringConverter::GetTrailingNumber (fields[3]); if (devices.size() > 0) { HostDevice &prevDev = **--devices.end(); if (string (hostDevice->Path).find (prevDev.Path) == 0) { prevDev.Partitions.push_back (hostDevice); continue; } } } catch (...) { } devices.push_back (hostDevice); continue; } catch (...) { continue; } } return devices; } MountedFilesystemList CoreLinux::GetMountedFilesystems (const DevicePath &devicePath, const DirectoryPath &mountPoint) const { MountedFilesystemList mountedFilesystems; DevicePath realDevicePath = devicePath; if (!devicePath.IsEmpty()) { char *resolvedPath = realpath (string (devicePath).c_str(), NULL); if (resolvedPath) { realDevicePath = resolvedPath; free (resolvedPath); } } FILE *mtab = fopen ("/etc/mtab", "r"); if (!mtab) mtab = fopen ("/proc/mounts", "r"); throw_sys_sub_if (!mtab, "/proc/mounts"); finally_do_arg (FILE *, mtab, { fclose (finally_arg); }); static Mutex mutex; ScopeLock sl (mutex); struct mntent *entry; while ((entry = getmntent (mtab)) != nullptr) { make_shared_auto (MountedFilesystem, mf); if (entry->mnt_fsname) mf->Device = DevicePath (entry->mnt_fsname); else continue; if (entry->mnt_dir) mf->MountPoint = DirectoryPath (entry->mnt_dir); if (entry->mnt_type) mf->Type = entry->mnt_type; if ((devicePath.IsEmpty() || devicePath == mf->Device || realDevicePath == mf->Device) && (mountPoint.IsEmpty() || mountPoint == mf->MountPoint)) mountedFilesystems.push_back (mf); } return mountedFilesystems; } void CoreLinux::MountFilesystem (const DevicePath &devicePath, const DirectoryPath &mountPoint, const string &filesystemType, bool readOnly, const string &systemMountOptions) const { bool fsMounted = false; try { if (!FilesystemSupportsUnixPermissions (devicePath)) { stringstream userMountOptions; userMountOptions << "uid=" << GetRealUserId() << ",gid=" << GetRealGroupId() << ",umask=077" << (!systemMountOptions.empty() ? "," : ""); CoreUnix::MountFilesystem (devicePath, mountPoint, filesystemType, readOnly, userMountOptions.str() + systemMountOptions); fsMounted = true; } } catch (...) { } if (!fsMounted) CoreUnix::MountFilesystem (devicePath, mountPoint, filesystemType, readOnly, systemMountOptions); } void CoreLinux::MountVolumeNative (shared_ptr volume, MountOptions &options, const DirectoryPath &auxMountPoint) const { bool xts = (typeid (*volume->GetEncryptionMode()) == typeid (EncryptionModeXTS)); if (options.NoKernelCrypto || !xts || volume->GetProtectionType() == VolumeProtection::HiddenVolumeReadOnly) { throw NotApplicable (SRC_POS); } if (!SystemInfo::IsVersionAtLeast (2, 6, xts ? 24 : 20)) throw NotApplicable (SRC_POS); // Load device mapper kernel module list execArgs; foreach (const string &dmModule, StringConverter::Split ("dm_mod dm-mod dm")) { execArgs.clear(); execArgs.push_back (dmModule); try { Process::Execute ("modprobe", execArgs); break; } catch (...) { } } bool loopDevAttached = false; bool nativeDevCreated = false; bool filesystemMounted = false; // Attach volume to loopback device if required VolumePath volumePath = volume->GetPath(); if (!volumePath.IsDevice()) { volumePath = AttachFileToLoopDevice (volumePath, options.Protection == VolumeProtection::ReadOnly); loopDevAttached = true; } string nativeDevPath; try { // Create virtual device using device mapper size_t nativeDevCount = 0; size_t secondaryKeyOffset = volume->GetEncryptionMode()->GetKey().Size(); size_t cipherCount = volume->GetEncryptionAlgorithm()->GetCiphers().size(); foreach_reverse_ref (const Cipher &cipher, volume->GetEncryptionAlgorithm()->GetCiphers()) { stringstream dmCreateArgs; dmCreateArgs << "0 " << volume->GetSize() / ENCRYPTION_DATA_UNIT_SIZE << " crypt "; // Mode dmCreateArgs << StringConverter::ToLower (StringConverter::ToSingle (cipher.GetName())) << (xts ? (SystemInfo::IsVersionAtLeast (2, 6, 33) ? "-xts-plain64 " : "-xts-plain ") : "-lrw-benbi "); size_t keyArgOffset = dmCreateArgs.str().size(); dmCreateArgs << setw (cipher.GetKeySize() * (xts ? 4 : 2) + (xts ? 0 : 16 * 2)) << 0 << setw (0); // Sector and data unit offset uint64 startSector = volume->GetLayout()->GetDataOffset (volume->GetHostSize()) / ENCRYPTION_DATA_UNIT_SIZE; dmCreateArgs << ' ' << (xts ? startSector + volume->GetEncryptionMode()->GetSectorOffset() : 0) << ' '; if (nativeDevCount == 0) dmCreateArgs << string (volumePath) << ' ' << startSector; else dmCreateArgs << nativeDevPath << " 0"; SecureBuffer dmCreateArgsBuf (dmCreateArgs.str().size()); dmCreateArgsBuf.CopyFrom (ConstBufferPtr ((byte *) dmCreateArgs.str().c_str(), dmCreateArgs.str().size())); // Keys const SecureBuffer &cipherKey = cipher.GetKey(); secondaryKeyOffset -= cipherKey.Size(); ConstBufferPtr secondaryKey = volume->GetEncryptionMode()->GetKey().GetRange (xts ? secondaryKeyOffset : 0, xts ? cipherKey.Size() : 16); SecureBuffer hexStr (3); for (size_t i = 0; i < cipherKey.Size(); ++i) { sprintf ((char *) hexStr.Ptr(), "%02x", (int) cipherKey[i]); dmCreateArgsBuf.GetRange (keyArgOffset + i * 2, 2).CopyFrom (hexStr.GetRange (0, 2)); sprintf ((char *) hexStr.Ptr(), "%02x", (int) secondaryKey[i]); dmCreateArgsBuf.GetRange (keyArgOffset + cipherKey.Size() * 2 + i * 2, 2).CopyFrom (hexStr.GetRange (0, 2)); } stringstream nativeDevName; nativeDevName << "veracrypt" << options.SlotNumber; if (nativeDevCount != cipherCount - 1) nativeDevName << "_" << cipherCount - nativeDevCount - 2; nativeDevPath = "/dev/mapper/" + nativeDevName.str(); execArgs.clear(); execArgs.push_back ("create"); execArgs.push_back (nativeDevName.str()); Process::Execute ("dmsetup", execArgs, -1, nullptr, &dmCreateArgsBuf); // Wait for the device to be created for (int t = 0; true; t++) { try { FilesystemPath (nativeDevPath).GetType(); break; } catch (...) { if (t > 20) throw; Thread::Sleep (100); } } nativeDevCreated = true; ++nativeDevCount; } // Test whether the device mapper is able to read and decrypt the last sector SecureBuffer lastSectorBuf (volume->GetSectorSize()); uint64 lastSectorOffset = volume->GetSize() - volume->GetSectorSize(); File nativeDev; nativeDev.Open (nativeDevPath); nativeDev.ReadAt (lastSectorBuf, lastSectorOffset); SecureBuffer lastSectorBuf2 (volume->GetSectorSize()); volume->ReadSectors (lastSectorBuf2, lastSectorOffset); if (memcmp (lastSectorBuf.Ptr(), lastSectorBuf2.Ptr(), volume->GetSectorSize()) != 0) throw KernelCryptoServiceTestFailed (SRC_POS); // Mount filesystem if (!options.NoFilesystem && options.MountPoint && !options.MountPoint->IsEmpty()) { MountFilesystem (nativeDevPath, *options.MountPoint, StringConverter::ToSingle (options.FilesystemType), options.Protection == VolumeProtection::ReadOnly, StringConverter::ToSingle (options.FilesystemOptions)); filesystemMounted = true; } FuseService::SendAuxDeviceInfo (auxMountPoint, nativeDevPath, volumePath); } catch (...) { try { if (filesystemMounted) DismountFilesystem (*options.MountPoint, true); } catch (...) { } try { if (nativeDevCreated) { make_shared_auto (VolumeInfo, vol); vol->VirtualDevice = nativeDevPath; DismountNativeVolume (vol); } } catch (...) { } try { if (loopDevAttached) DetachLoopDevice (volumePath); } catch (...) { } throw; } } auto_ptr Core (new CoreServiceProxy ); auto_ptr CoreDirect (new CoreLinux); }