--- a/CPP/7zip/UI/Agent/Agent.cpp +++ b/CPP/7zip/UI/Agent/Agent.cpp @@ -424,6 +424,8 @@ STDMETHODIMP CAgentFolder::Extract(const CMyComPtr extractCallback = extractCallbackSpec; UStringVector pathParts; CProxyFolder *currentProxyFolder = _proxyFolderItem; + HRESULT res; + while (currentProxyFolder->Parent) { pathParts.Insert(0, currentProxyFolder->Name); @@ -445,8 +447,11 @@ STDMETHODIMP CAgentFolder::Extract(const (UInt64)(Int64)-1); CUIntVector realIndices; GetRealIndices(indices, numItems, realIndices); - return _agentSpec->GetArchive()->Extract(&realIndices.Front(), + res = _agentSpec->GetArchive()->Extract(&realIndices.Front(), realIndices.Size(), testMode, extractCallback); + if (res == S_OK && !extractCallbackSpec->CreateSymLinks()) + res = E_FAIL; + return res; COM_TRY_END } --- a/CPP/7zip/UI/Agent/ArchiveFolder.cpp +++ b/CPP/7zip/UI/Agent/ArchiveFolder.cpp @@ -20,6 +20,8 @@ STDMETHODIMP CAgentFolder::CopyTo(const CMyComPtr extractCallback = extractCallbackSpec; UStringVector pathParts; CProxyFolder *currentProxyFolder = _proxyFolderItem; + HRESULT res; + while (currentProxyFolder->Parent) { pathParts.Insert(0, currentProxyFolder->Name); @@ -46,8 +48,11 @@ STDMETHODIMP CAgentFolder::CopyTo(const (UInt64)(Int64)-1); CUIntVector realIndices; GetRealIndices(indices, numItems, realIndices); - return _agentSpec->GetArchive()->Extract(&realIndices.Front(), + res = _agentSpec->GetArchive()->Extract(&realIndices.Front(), realIndices.Size(), BoolToInt(false), extractCallback); + if (res == S_OK && !extractCallbackSpec->CreateSymLinks()) + res = E_FAIL; + return res; COM_TRY_END } --- a/CPP/7zip/UI/Client7z/Client7z.cpp +++ b/CPP/7zip/UI/Client7z/Client7z.cpp @@ -197,8 +197,11 @@ private: COutFileStream *_outFileStreamSpec; CMyComPtr _outFileStream; + CObjectVector _delayedSymLinks; + public: void Init(IInArchive *archiveHandler, const UString &directoryPath); + bool CreateSymLinks(); UInt64 NumErrors; bool PasswordIsDefined; @@ -392,11 +395,22 @@ STDMETHODIMP CArchiveExtractCallback::Se } _outFileStream.Release(); if (_extractMode && _processedFileInfo.AttribDefined) - NFile::NDirectory::MySetFileAttributes(_diskFilePath, _processedFileInfo.Attrib); + NFile::NDirectory::MySetFileAttributes(_diskFilePath, _processedFileInfo.Attrib, &_delayedSymLinks); PrintNewLine(); return S_OK; } +bool CArchiveExtractCallback::CreateSymLinks() +{ + bool success = true; + + for (int i = 0; i != _delayedSymLinks.Size(); ++i) + success &= _delayedSymLinks[i].Create(); + + _delayedSymLinks.Clear(); + + return success; +} STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password) { --- a/CPP/7zip/UI/Common/ArchiveExtractCallback.cpp +++ b/CPP/7zip/UI/Common/ArchiveExtractCallback.cpp @@ -453,12 +453,24 @@ STDMETHODIMP CArchiveExtractCallback::Se NumFiles++; if (_extractMode && _fi.AttribDefined) - NFile::NDirectory::MySetFileAttributes(_diskFilePath, _fi.Attrib); + NFile::NDirectory::MySetFileAttributes(_diskFilePath, _fi.Attrib, &_delayedSymLinks); RINOK(_extractCallback2->SetOperationResult(operationResult, _encrypted)); return S_OK; COM_TRY_END } +bool CArchiveExtractCallback::CreateSymLinks() +{ + bool success = true; + + for (int i = 0; i != _delayedSymLinks.Size(); ++i) + success &= _delayedSymLinks[i].Create(); + + _delayedSymLinks.Clear(); + + return success; +} + /* STDMETHODIMP CArchiveExtractCallback::GetInStream( const wchar_t *name, ISequentialInStream **inStream) --- a/CPP/7zip/UI/Common/ArchiveExtractCallback.h +++ b/CPP/7zip/UI/Common/ArchiveExtractCallback.h @@ -6,6 +6,8 @@ #include "Common/MyCom.h" #include "Common/Wildcard.h" +#include "Windows/FileDir.h" + #include "../../IPassword.h" #include "../../Common/FileStreams.h" @@ -83,6 +85,8 @@ class CArchiveExtractCallback: UInt64 _packTotal; UInt64 _unpTotal; + CObjectVector _delayedSymLinks; + void CreateComplexDirectory(const UStringVector &dirPathParts, UString &fullPath); HRESULT GetTime(int index, PROPID propID, FILETIME &filetime, bool &filetimeIsDefined); HRESULT GetUnpackSize(); @@ -138,6 +142,7 @@ public: const UStringVector &removePathParts, UInt64 packSize); + bool CreateSymLinks(); }; #endif --- a/CPP/7zip/UI/Common/Extract.cpp +++ b/CPP/7zip/UI/Common/Extract.cpp @@ -96,6 +96,9 @@ static HRESULT DecompressArchive( else result = archive->Extract(&realIndices.Front(), realIndices.Size(), testMode, extractCallbackSpec); + if (result == S_OK && !extractCallbackSpec->CreateSymLinks()) + result = E_FAIL; + return callback->ExtractResult(result); } --- a/CPP/Windows/FileDir.cpp +++ b/CPP/Windows/FileDir.cpp @@ -453,9 +453,10 @@ bool SetDirTime(LPCWSTR fileName, const } #ifndef _UNICODE -bool MySetFileAttributes(LPCWSTR fileName, DWORD fileAttributes) +bool MySetFileAttributes(LPCWSTR fileName, DWORD fileAttributes, + CObjectVector *delayedSymLinks) { - return MySetFileAttributes(UnicodeStringToMultiByte(fileName, CP_ACP), fileAttributes); + return MySetFileAttributes(UnicodeStringToMultiByte(fileName, CP_ACP), fileAttributes, delayedSymLinks); } bool MyRemoveDirectory(LPCWSTR pathName) @@ -488,7 +489,8 @@ static int convert_to_symlink(const char return -1; } -bool MySetFileAttributes(LPCTSTR fileName, DWORD fileAttributes) +bool MySetFileAttributes(LPCTSTR fileName, DWORD fileAttributes, + CObjectVector *delayedSymLinks) { if (!fileName) { SetLastError(ERROR_PATH_NOT_FOUND); @@ -520,7 +522,9 @@ bool MySetFileAttributes(LPCTSTR fileNam stat_info.st_mode = fileAttributes >> 16; #ifdef ENV_HAVE_LSTAT if (S_ISLNK(stat_info.st_mode)) { - if ( convert_to_symlink(name) != 0) { + if (delayedSymLinks) + delayedSymLinks->Add(CDelayedSymLink(name)); + else if ( convert_to_symlink(name) != 0) { TRACEN((printf("MySetFileAttributes(%s,%d) : false-3\n",name,fileAttributes))) return false; } @@ -924,4 +928,41 @@ bool CTempDirectory::Create(LPCTSTR pref } +#ifdef ENV_UNIX + +CDelayedSymLink::CDelayedSymLink(LPCSTR source) + : _source(source) +{ + struct stat st; + + if (lstat(_source, &st) == 0) { + _dev = st.st_dev; + _ino = st.st_ino; + } else { + _dev = 0; + } +} + +bool CDelayedSymLink::Create() +{ + struct stat st; + + if (_dev == 0) { + errno = EPERM; + return false; + } + if (lstat(_source, &st) != 0) + return false; + if (_dev != st.st_dev || _ino != st.st_ino) { + // Placeholder file has been overwritten or moved by another + // symbolic link creation + errno = EPERM; + return false; + } + + return convert_to_symlink(_source) == 0; +} + +#endif // ENV_UNIX + }}} --- a/CPP/Windows/FileDir.h +++ b/CPP/Windows/FileDir.h @@ -4,6 +4,7 @@ #define __WINDOWS_FILEDIR_H #include "../Common/MyString.h" +#include "../Common/MyVector.h" #include "Defs.h" /* GetFullPathName for 7zAES.cpp */ @@ -13,11 +14,15 @@ namespace NWindows { namespace NFile { namespace NDirectory { +class CDelayedSymLink; + bool SetDirTime(LPCWSTR fileName, const FILETIME *creationTime, const FILETIME *lastAccessTime, const FILETIME *lastWriteTime); -bool MySetFileAttributes(LPCTSTR fileName, DWORD fileAttributes); +bool MySetFileAttributes(LPCTSTR fileName, DWORD fileAttributes, + CObjectVector *delayedSymLinks = 0); #ifndef _UNICODE -bool MySetFileAttributes(LPCWSTR fileName, DWORD fileAttributes); +bool MySetFileAttributes(LPCWSTR fileName, DWORD fileAttributes, + CObjectVector *delayedSymLinks = 0); #endif bool MyMoveFile(LPCTSTR existFileName, LPCTSTR newFileName); @@ -80,6 +85,31 @@ public: bool Remove(); }; +// Symbolic links must be created last so that they can't be used to +// create or overwrite files above the extraction directory. +class CDelayedSymLink +{ +#ifdef ENV_UNIX + // Where the symlink should be created. The target is specified in + // the placeholder file. + AString _source; + + // Device and inode of the placeholder file. Before creating the + // symlink, we must check that these haven't been changed by creation + // of another symlink. + dev_t _dev; + ino_t _ino; + +public: + explicit CDelayedSymLink(LPCSTR source); + bool Create(); +#else // !ENV_UNIX +public: + CDelayedSymLink(LPCSTR source) {} + bool Create() { return true; } +#endif // ENV_UNIX +}; + #ifdef _UNICODE typedef CTempFile CTempFileW; #endif