summaryrefslogtreecommitdiff
path: root/sal
diff options
context:
space:
mode:
authorMichael Meeks <michael.meeks@collabora.com>2023-11-22 19:37:38 +0000
committerSzymon Kłos <szymon.klos@collabora.com>2024-01-15 21:07:27 +0100
commit5e66863ae42bf0e5b614a8f21dc44a15df727922 (patch)
tree3c65f697751207618627de6d038838157c0c70c9 /sal
parent165a3995d396455eaf0482f407211f7e9ba61923 (diff)
sal: initial osl::File sand-boxing commit for Unix.
Change-Id: If2c106fef9640499b82b5cf350cb5169beb219fb Reviewed-on: https://gerrit.libreoffice.org/c/core/+/159838 Reviewed-by: Szymon Kłos <szymon.klos@collabora.com> Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com> Reviewed-by: Michael Meeks <michael.meeks@collabora.com>
Diffstat (limited to 'sal')
-rw-r--r--sal/osl/unx/file.cxx124
-rw-r--r--sal/osl/unx/file_impl.hxx2
-rw-r--r--sal/osl/unx/file_misc.cxx34
-rw-r--r--sal/osl/unx/file_stat.cxx6
-rw-r--r--sal/osl/unx/file_volume.cxx4
-rw-r--r--sal/osl/unx/pipe.cxx4
-rw-r--r--sal/osl/unx/process.cxx5
-rw-r--r--sal/osl/unx/profile.cxx4
-rw-r--r--sal/qa/osl/file/osl_File.cxx118
-rw-r--r--sal/util/sal.map6
10 files changed, 306 insertions, 1 deletions
diff --git a/sal/osl/unx/file.cxx b/sal/osl/unx/file.cxx
index eeee7c803fd8..848a586ad2ce 100644
--- a/sal/osl/unx/file.cxx
+++ b/sal/osl/unx/file.cxx
@@ -784,6 +784,125 @@ static bool osl_file_queryLocking(sal_uInt32 uFlags)
return false;
}
+static bool abortOnForbidden = false;
+static std::vector<OString> allowedPathsRead;
+static std::vector<OString> allowedPathsReadWrite;
+static std::vector<OString> allowedPathsExecute;
+
+SAL_DLLPUBLIC void osl_setAllowedPaths(
+ rtl_uString *pustrFilePaths
+ )
+{
+ allowedPathsRead.clear();
+ allowedPathsReadWrite.clear();
+ allowedPathsExecute.clear();
+
+ if (!pustrFilePaths)
+ return;
+
+ char eType = 'r';
+ sal_Int32 nIndex = 0;
+ OUString aPaths(pustrFilePaths);
+ do
+ {
+ OString aPath = rtl::OUStringToOString(
+ aPaths.getToken(0, ':', nIndex),
+ RTL_TEXTENCODING_UTF8);
+
+ if (aPath.getLength() == 0)
+ continue;
+
+ if (aPath.getLength() == 1)
+ {
+ eType = aPath[0];
+ continue;
+ }
+
+ char resolvedPath[PATH_MAX];
+ if (realpath(aPath.getStr(), resolvedPath))
+ {
+ OString aPushPath = OString(resolvedPath, strlen(resolvedPath));
+ if (eType == 'r')
+ allowedPathsRead.push_back(aPushPath);
+ else if (eType == 'w')
+ {
+ allowedPathsRead.push_back(aPushPath);
+ allowedPathsReadWrite.push_back(aPushPath);
+ }
+ else if (eType == 'x')
+ allowedPathsExecute.push_back(aPushPath);
+ }
+ }
+ while (nIndex != -1);
+
+ abortOnForbidden = !!getenv("SAL_ABORT_ON_FORBIDDEN");
+}
+
+bool isForbidden(const OString &filePath, int nFlags)
+{
+ // avoid realpath cost unless configured
+ if (allowedPathsRead.size() == 0)
+ return false;
+
+ char resolvedPath[PATH_MAX];
+ if (!realpath(filePath.getStr(), resolvedPath))
+ {
+ // write calls path a non-existent path that realpath will
+ // fail to resolve. Thankfully our I/O APIs don't allow
+ // symlink creation to race here.
+ sal_Int32 n = filePath.lastIndexOf('/');
+ OString folderPath;
+ if (n < 1)
+ folderPath = ".";
+ else
+ folderPath = filePath.copy(0, n);
+ if (!realpath(folderPath.getStr(), resolvedPath) ||
+ strlen(resolvedPath) + filePath.getLength() - n + 1 >= PATH_MAX)
+ return true; // too bad
+ else
+ strcat(resolvedPath, filePath.getStr() + n);
+ }
+
+ const std::vector<OString> *pCheckPaths = &allowedPathsRead;
+ if (nFlags & osl_File_OpenFlag_Write ||
+ nFlags & osl_File_OpenFlag_Create)
+ pCheckPaths = &allowedPathsReadWrite;
+ else if (nFlags & 0x80)
+ pCheckPaths = &allowedPathsExecute;
+
+ bool allowed = false;
+ for (const auto &it : *pCheckPaths) {
+ if (!strncmp(resolvedPath, it.getStr(), it.getLength()))
+ {
+ allowed = true;
+ break;
+ }
+ }
+
+ if (!allowed)
+ SAL_WARN("sal.osl", "access outside sandbox to " <<
+ ((nFlags & osl_File_OpenFlag_Write ||
+ nFlags & osl_File_OpenFlag_Create) ? "w" :
+ (nFlags & 0x80) ? "x" : "r") << ":" <<
+ filePath << " which is really " << resolvedPath <<
+ (allowed ? " allowed " : " forbidden") <<
+ " check list: " << pCheckPaths->size());
+
+ if (abortOnForbidden && !allowed)
+ abort(); // a bit abrupt - but don't try to escape.
+
+ return !allowed;
+}
+
+SAL_DLLPUBLIC sal_Bool SAL_CALL osl_isForbiddenPath(
+ rtl_uString *pustrFileURL, int nFlags
+ )
+{
+ return isForbidden(
+ rtl::OUStringToOString(OUString(pustrFileURL),
+ RTL_TEXTENCODING_UTF8), nFlags);
+}
+
#ifdef HAVE_O_EXLOCK
#define OPEN_WRITE_FLAGS ( O_RDWR | O_EXLOCK | O_NONBLOCK )
#define OPEN_CREATE_FLAGS ( O_CREAT | O_RDWR | O_EXLOCK | O_NONBLOCK )
@@ -988,6 +1107,10 @@ oslFileError openFilePath(const OString& filePath, oslFileHandle* pHandle,
if (flags & O_EXCL && !(flags & O_CREAT))
flags &= ~O_EXCL;
+ // Sandboxing hook
+ if (isForbidden( filePath, uFlags ))
+ return osl_File_E_ACCES;
+
/* open the file */
int fd = open_c( filePath, flags, mode );
if (fd == -1)
@@ -1602,4 +1725,5 @@ oslFileError SAL_CALL osl_setFileSize(oslFileHandle Handle, sal_uInt64 uSize)
return pImpl->setSize(uSize);
}
+
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sal/osl/unx/file_impl.hxx b/sal/osl/unx/file_impl.hxx
index 4a9a90d4160f..64504296955d 100644
--- a/sal/osl/unx/file_impl.hxx
+++ b/sal/osl/unx/file_impl.hxx
@@ -40,6 +40,8 @@ struct DirectoryItem_Impl
oslFileType getFileType() const;
};
+bool isForbidden(const OString &filePath, int nFlags);
+
oslFileError openFile(
rtl_uString * pustrFileURL, oslFileHandle * pHandle, sal_uInt32 uFlags,
mode_t mode);
diff --git a/sal/osl/unx/file_misc.cxx b/sal/osl/unx/file_misc.cxx
index 752c85393b86..69ca29cdbaa4 100644
--- a/sal/osl/unx/file_misc.cxx
+++ b/sal/osl/unx/file_misc.cxx
@@ -143,6 +143,9 @@ oslFileError SAL_CALL osl_openDirectory(rtl_uString* ustrDirectoryURL, oslDirect
osl_systemPathRemoveSeparator(path.pData);
+ if (isForbidden(path.getStr(), osl_File_OpenFlag_Read))
+ return osl_File_E_ACCES;
+
#ifdef MACOSX
{
auto const n = std::max(int(path.getLength() + 1), int(PATH_MAX));
@@ -343,6 +346,9 @@ oslFileError SAL_CALL osl_getDirectoryItem(rtl_uString* ustrFileURL, oslDirector
osl_systemPathRemoveSeparator(strSystemPath.pData);
+ if (isForbidden(strSystemPath, osl_File_OpenFlag_Read))
+ return osl_File_E_ACCES;
+
if (osl::access(strSystemPath, F_OK) == -1)
{
osl_error = oslTranslateFileError(errno);
@@ -426,6 +432,9 @@ oslFileError SAL_CALL osl_removeDirectory( rtl_uString* ustrDirectoryURL )
oslFileError osl_psz_createDirectory(char const * pszPath, sal_uInt32 flags)
{
+ if (isForbidden(pszPath, osl_File_OpenFlag_Create))
+ return osl_File_E_ACCES;
+
int nRet=0;
int mode
= (((flags & osl_File_OpenFlag_Read) == 0
@@ -455,6 +464,9 @@ oslFileError osl_psz_createDirectory(char const * pszPath, sal_uInt32 flags)
static oslFileError osl_psz_removeDirectory( const char* pszPath )
{
+ if (isForbidden(pszPath, osl_File_OpenFlag_Write))
+ return osl_File_E_ACCES;
+
int nRet = rmdir(pszPath);
if ( nRet < 0 )
@@ -550,6 +562,9 @@ oslFileError SAL_CALL osl_createDirectoryPath(
osl::systemPathRemoveSeparator(sys_path);
+ if (isForbidden(sys_path.getStr(), osl_File_OpenFlag_Create))
+ return osl_File_E_ACCES;
+
// const_cast because sys_path is a local copy which we want to modify inplace instead of
// copy it into another buffer on the heap again
return create_dir_recursively_(sys_path.pData->buffer, aDirectoryCreationCallbackFunc, pData);
@@ -584,6 +599,10 @@ oslFileError SAL_CALL osl_moveFile( rtl_uString* ustrFileURL, rtl_uString* ustrD
if( eRet != osl_File_E_None )
return eRet;
+ if (isForbidden(srcPath, osl_File_OpenFlag_Read) ||
+ isForbidden(destPath, osl_File_OpenFlag_Create))
+ return osl_File_E_ACCES;
+
#ifdef MACOSX
if ( macxp_resolveAlias( srcPath, PATH_MAX ) != 0 || macxp_resolveAlias( destPath, PATH_MAX ) != 0 )
return oslTranslateFileError( errno );
@@ -597,6 +616,10 @@ oslFileError SAL_CALL osl_replaceFile(rtl_uString* ustrFileURL, rtl_uString* ust
int nGid = -1;
char destPath[PATH_MAX];
oslFileError eRet = FileURLToPath(destPath, PATH_MAX, ustrDestURL);
+
+ if (isForbidden(destPath, osl_File_OpenFlag_Create))
+ return osl_File_E_ACCES;
+
if (eRet == osl_File_E_None)
{
struct stat aFileStat;
@@ -673,6 +696,9 @@ oslFileError SAL_CALL osl_removeFile(rtl_uString* ustrFileURL)
return oslTranslateFileError(errno);
#endif/* MACOSX */
+ if (isForbidden(path, osl_File_OpenFlag_Write))
+ return osl_File_E_ACCES;
+
return osl_unlinkFile(path);
}
@@ -728,6 +754,10 @@ static oslFileError osl_unlinkFile(const char* pszPath)
static oslFileError osl_psz_moveFile(const char* pszPath, const char* pszDestPath)
{
+ if (isForbidden(pszPath, osl_File_OpenFlag_Read) ||
+ isForbidden(pszDestPath, osl_File_OpenFlag_Create))
+ return osl_File_E_ACCES;
+
int nRet = rename(pszPath,pszDestPath);
if (nRet < 0)
@@ -755,6 +785,10 @@ static oslFileError osl_psz_copyFile( const char* pszPath, const char* pszDestPa
size_t nSourceSize=0;
bool DestFileExists=true;
+ if (isForbidden(pszPath, osl_File_OpenFlag_Read) ||
+ isForbidden(pszDestPath, osl_File_OpenFlag_Create))
+ return osl_File_E_ACCES;
+
/* mfe: does the source file really exists? */
nRet = lstat_c(pszPath,&aFileStat);
diff --git a/sal/osl/unx/file_stat.cxx b/sal/osl/unx/file_stat.cxx
index 5c165132e9f3..3d0754883a11 100644
--- a/sal/osl/unx/file_stat.cxx
+++ b/sal/osl/unx/file_stat.cxx
@@ -274,6 +274,9 @@ static oslFileError osl_psz_setFileAttributes( const char* pszFilePath, sal_uInt
OSL_ENSURE(!(osl_File_Attribute_Hidden & uAttributes), "osl_File_Attribute_Hidden doesn't work under Unix");
+ if (isForbidden(pszFilePath, osl_File_OpenFlag_Write))
+ return osl_File_E_ACCES;
+
if (uAttributes & osl_File_Attribute_OwnRead)
nNewMode |= S_IRUSR;
@@ -339,6 +342,9 @@ static oslFileError osl_psz_setFileTime (
struct tm* pTM=0;
#endif
+ if (isForbidden(pszFilePath, osl_File_OpenFlag_Write))
+ return osl_File_E_ACCES;
+
nRet = lstat_c(pszFilePath,&aFileStat);
if ( nRet < 0 )
diff --git a/sal/osl/unx/file_volume.cxx b/sal/osl/unx/file_volume.cxx
index e20b8a27d00e..0a6183292735 100644
--- a/sal/osl/unx/file_volume.cxx
+++ b/sal/osl/unx/file_volume.cxx
@@ -26,6 +26,7 @@
#include "file_error_transl.hxx"
#include "file_url.hxx"
+#include "file_impl.hxx"
#include "system.hxx"
#include <errno.h>
@@ -193,6 +194,9 @@ static oslFileError osl_psz_getVolumeInformation (
if (!pInfo)
return osl_File_E_INVAL;
+ if (isForbidden(pszDirectory, osl_File_OpenFlag_Read))
+ return osl_File_E_ACCES;
+
pInfo->uValidFields = 0;
pInfo->uAttributes = 0;
pInfo->uTotalSpace = 0;
diff --git a/sal/osl/unx/pipe.cxx b/sal/osl/unx/pipe.cxx
index 4dfd75ddf662..62897552ed41 100644
--- a/sal/osl/unx/pipe.cxx
+++ b/sal/osl/unx/pipe.cxx
@@ -29,6 +29,7 @@
#include "sockimpl.hxx"
#include "secimpl.hxx"
+#include "file_impl.hxx"
#include "unixerrnostring.hxx"
#include <cassert>
@@ -209,6 +210,9 @@ static oslPipe osl_psz_createPipe(const char *pszPipeName, oslPipeOptions Option
SAL_INFO("sal.osl.pipe", "new pipe on fd " << pPipe->m_Socket << " '" << name << "'");
+ if (isForbidden(name.getStr(), osl_File_OpenFlag_Create))
+ return nullptr;
+
addr.sun_family = AF_UNIX;
// coverity[fixed_size_dest : FALSE] - safe, see check above
strcpy(addr.sun_path, name.getStr());
diff --git a/sal/osl/unx/process.cxx b/sal/osl/unx/process.cxx
index cebdc6f35fdb..1d1548129f6d 100644
--- a/sal/osl/unx/process.cxx
+++ b/sal/osl/unx/process.cxx
@@ -19,7 +19,7 @@
#include <sal/config.h>
#include <rtl/ustring.hxx>
-
+#include "file_impl.hxx"
#include <cassert>
#include <fcntl.h>
#include <limits.h>
@@ -598,6 +598,9 @@ oslProcessError osl_psz_executeProcess(char *pszImageName,
return osl_Process_E_NotFound;
}
+ if (isForbidden(pszImageName, 0x80 /* execute */))
+ return osl_Process_E_NoPermission;
+
Data.m_pszArgs[0] = strdup(pszImageName);
Data.m_pszArgs[1] = nullptr;
diff --git a/sal/osl/unx/profile.cxx b/sal/osl/unx/profile.cxx
index 1e7512a24db4..1ef1965b7c14 100644
--- a/sal/osl/unx/profile.cxx
+++ b/sal/osl/unx/profile.cxx
@@ -20,6 +20,7 @@
#include "system.hxx"
#include "readwrite_helper.hxx"
#include "file_url.hxx"
+#include "file_impl.hxx"
#include "unixerrnostring.hxx"
#include <osl/diagnose.h>
@@ -937,6 +938,9 @@ static osl_TFile* openFileImpl(const char* pszFilename, oslProfileOption Profile
osl_TFile* pFile = static_cast<osl_TFile*>(calloc(1, sizeof(osl_TFile)));
bool bWriteable = false;
+ if ( isForbidden( pszFilename, osl_File_OpenFlag_Write ) )
+ return nullptr;
+
if ( ProfileFlags & ( osl_Profile_WRITELOCK | osl_Profile_FLUSHWRITE ) )
{
bWriteable = true;
diff --git a/sal/qa/osl/file/osl_File.cxx b/sal/qa/osl/file/osl_File.cxx
index 6b0c50dfafd8..0de87eeb14a9 100644
--- a/sal/qa/osl/file/osl_File.cxx
+++ b/sal/qa/osl/file/osl_File.cxx
@@ -1313,6 +1313,124 @@ namespace osl_FileBase
CPPUNIT_REGISTRY_ADD_TO_DEFAULT("osl_osl::FileBase");
}
+#if (defined UNX)
+
+namespace osl_Forbidden
+{
+
+ class Forbidden : public CppUnit::TestFixture
+ {
+ OUString maScratchBad;
+ OUString maScratchGood;
+ public:
+ void setUp() override
+ {
+ // create a directory to play in
+ createTestDirectory(aTmpName3);
+ OUString aBadURL = aTmpName3 + "/bad";
+ OUString aGoodURL = aTmpName3 + "/good";
+ createTestDirectory(aBadURL);
+ createTestDirectory(aGoodURL);
+ File::getSystemPathFromFileURL(aBadURL, maScratchBad);
+ File::getSystemPathFromFileURL(aGoodURL, maScratchGood);
+ }
+
+ void tearDown() override
+ {
+ osl_setAllowedPaths(nullptr);
+ OUString aBadURL = aTmpName3 + "/bad";
+ OUString aGoodURL = aTmpName3 + "/good";
+ deleteTestDirectory(aBadURL);
+ deleteTestDirectory(aGoodURL);
+ deleteTestDirectory(aTmpName3);
+ }
+
+ void forbidden()
+ {
+ File::setAllowedPaths(maScratchGood);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("read bad should be forbidden",
+ true, File::isForbidden(maScratchBad, osl_File_OpenFlag_Read));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("read from good should be allowed",
+ false, File::isForbidden(maScratchGood, osl_File_OpenFlag_Read));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("write to good should be forbidden",
+ true, File::isForbidden(maScratchGood, osl_File_OpenFlag_Write));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("create in good should be forbidden",
+ true, File::isForbidden(maScratchGood, osl_File_OpenFlag_Create));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("exec from good should be forbidden",
+ true, File::isForbidden(maScratchGood, 0x80));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("write to non-existent should be forbidden",
+ true, File::isForbidden(maScratchBad + "/notthere", osl_File_OpenFlag_Write));
+
+ File::setAllowedPaths("w:" + maScratchGood + ":x:" + maScratchBad);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("read bad should be forbidden",
+ true, File::isForbidden(maScratchBad, osl_File_OpenFlag_Read));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("read from good should be allowed", // w implies 'r'
+ false, File::isForbidden(maScratchGood, osl_File_OpenFlag_Read));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("write to good should be allowed",
+ false, File::isForbidden(maScratchGood, osl_File_OpenFlag_Write));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("exec from good should be forbidden",
+ true, File::isForbidden(maScratchGood, 0x80));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("exec from bad should be allowed",
+ false, File::isForbidden(maScratchBad, 0x80));
+ }
+
+ void open()
+ {
+ File::setAllowedPaths(maScratchGood);
+ File testFile(maScratchBad + "/open");
+ auto nError1 = testFile.open(osl_File_OpenFlag_Read | osl_File_OpenFlag_Write);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("disabled path allowed", osl::FileBase::E_ACCES, nError1);
+ deleteTestFile(testFile.getURL());
+ }
+
+ void copy()
+ {
+ File::setAllowedPaths("w:" + maScratchGood);
+ File testGood(maScratchGood + "/good");
+ File testGoodTo(maScratchGood + "/good_to");
+ File testBad(maScratchBad + "/bad");
+
+ auto nError1 = testGood.open(osl_File_OpenFlag_Create);
+ CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, nError1);
+
+ auto nErrorCopy = File::copy(maScratchGood + "/good", maScratchGood + "/good_to");
+ CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, nErrorCopy);
+
+ auto nErrorCopyBad = File::copy(maScratchGood + "/good_to", maScratchBad + "/bad");
+ CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_ACCES, nErrorCopyBad);
+
+ deleteTestFile(maScratchGood + "/good_to");
+ deleteTestFile(maScratchGood + "/good");
+ }
+
+ void nextTests()
+ {
+ // more entry points to test
+#if 0
+ auto nError1 = File::move(aTmpName4, aCanURL1);
+ auto nError2 = File::remove(aTmpName4);
+ auto nError3 = File::setAttributes(aTmpName6, osl_File_Attribute_ReadOnly);
+ bool bOk = osl_getSystemTime(pTV_current);
+ CPPUNIT_ASSERT(bOk);
+ auto nError4 = File::setTime(aTmpName6, *pTV_current, *pTV_current, *pTV_current);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(errorToStr(nError2).getStr(), osl::FileBase::E_None, nError2);
+#endif
+ }
+
+ CPPUNIT_TEST_SUITE(Forbidden);
+ CPPUNIT_TEST(forbidden);
+// CPPUNIT_TEST(open);
+// CPPUNIT_TEST(copy);
+// CPPUNIT_TEST(nextTests);
+ CPPUNIT_TEST_SUITE_END();
+ };
+
+ CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(osl_Forbidden::Forbidden, "osl_Forbidden");
+
+ CPPUNIT_REGISTRY_ADD_TO_DEFAULT("osl_Forbidden");
+}
+#endif
+
namespace osl_FileStatus
{
// testing the method
diff --git a/sal/util/sal.map b/sal/util/sal.map
index c5c3e4d55641..586a41ee997d 100644
--- a/sal/util/sal.map
+++ b/sal/util/sal.map
@@ -761,6 +761,12 @@ PRIVATE_1.8 { # LibreOffice 7.3
rtl_uString_newReplaceStrAtUtf16L;
} PRIVATE_1.7;
+PRIVATE_1.9 { # LibreOffice 7.7
+ global:
+ osl_setAllowedPaths;
+ osl_isForbiddenPath;
+} PRIVATE_1.8;
+
PRIVATE_textenc.1 { # LibreOffice 3.6
global:
_ZN3sal6detail7textenc20convertCharToUnicode*;