desktop/source/lib/init.cxx | 9 - include/rtl/alloc.h | 8 - sal/CppunitTest_sal_osl_file_details.mk | 65 ++++++++ sal/Library_sal.mk | 1 sal/Module_sal.mk | 1 sal/inc/setallowedpaths.hxx | 39 ++++ sal/osl/unx/file.cxx | 145 ++++++++++++++++++ sal/osl/unx/file_impl.hxx | 19 ++ sal/osl/unx/file_misc.cxx | 34 ++++ sal/osl/unx/file_stat.cxx | 6 sal/osl/unx/file_volume.cxx | 4 sal/osl/unx/pipe.cxx | 4 sal/osl/unx/process.cxx | 5 sal/osl/unx/profile.cxx | 4 sal/qa/osl/file/forbidden.cxx | 254 ++++++++++++++++++++++++++++++++ sal/qa/rtl/alloc/rtl_alloc.cxx | 4 sal/rtl/preinit.cxx | 51 ++++++ sal/rtl/strimp.cxx | 2 sal/rtl/strimp.hxx | 2 solenv/clang-format/excludelist | 1 20 files changed, 648 insertions(+), 10 deletions(-)
New commits: commit 19e5551130ea78e7d1ee79c51dc90f80a3c7425c Author: Stephan Bergmann <stephan.bergm...@collabora.com> AuthorDate: Fri Jul 4 17:20:34 2025 +0200 Commit: Stephan Bergmann <stephan.bergm...@collabora.com> CommitDate: Mon Jul 7 15:37:55 2025 +0200 sal: osl::File sand-boxing for Unix This is a combined cherry-pick of the 10 commits 34f1a750a128e02aa3191e36a2c0b28371e96287 "sal: initial osl::File sand-boxing commit for Unix.", 55243a397add76fcff8c67284ba57205a1512ca5 "lok: import allowed paths from the SAL_ALLOWED_PATHS.", 148915ef6d4bfc38deb6d98fb14429d7e6e27129 "sal: osl::File allow to create files in sandbox", 479e2f5cb69ceef2ef6832630a342d7673d6782a "unconditionally include vector", f04d38d5a88f422b80d2a37d1a1252ce59c4f892 "Fix SDK build", 52e0838a8f35837a92e7b0277544995e477fe8b6 "sal: fix various clang loplugin warnings", 3ed325340d14cb28602fb03eb6a46b37cdb8bb99 "[loplugin:unreffun]", 242ccc31d5a2c8be900828523de145e173900514 "Avoid extending the sal UNO API", 3c633a88c5212d8dd665d7bcb9678fa1eac1464f "Stick to sal_uInt32 for osl_File_OpenFlags", and f4d48ac9cdb822e2c2d3fc1f239fe0958f608cc2 "Fix linking new CppunitTest_sal_osl_file_details on macOS/Windows". Co-authored-by: Andras Timar <andras.ti...@collabora.com> Co-authored-by: Caolán McNamara <caolan.mcnam...@collabora.com> Co-authored-by: Michael Meeks <michael.me...@collabora.com> Co-authored-by: Mike Kaganski <mike.kagan...@collabora.com> Co-authored-by: Miklos Vajna <vmik...@collabora.com> Co-authored-by: Szymon Kłos <szymon.k...@collabora.com> Change-Id: I5220bef367350d8fac8c25d7f40988664b9a8bd5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187411 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <stephan.bergm...@collabora.com> diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 83c79026d57a..549d72786ccc 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -8255,6 +8255,11 @@ static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char } } +#ifdef LINUX + // Enforce SAL_ALLOWED_PATHS: + rtl_alloc_preInit(2); +#endif + char* pAllowlist = ::getenv("LOK_HOST_ALLOWLIST"); if (pAllowlist) { @@ -8292,7 +8297,7 @@ static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char if (eStage == PRE_INIT) { - rtl_alloc_preInit(true); + rtl_alloc_preInit(1); // Set the default timezone to the TZ envar, if set. const char* tz = ::getenv("TZ"); @@ -8599,7 +8604,7 @@ static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char // staticize all strings. if (eStage == PRE_INIT) - rtl_alloc_preInit(false); + rtl_alloc_preInit(0); return bInitialized; } diff --git a/include/rtl/alloc.h b/include/rtl/alloc.h index 1dc9cefacb0d..bfcc9a02b186 100644 --- a/include/rtl/alloc.h +++ b/include/rtl/alloc.h @@ -298,8 +298,8 @@ SAL_DLLPUBLIC void SAL_CALL rtl_cache_free ( * at the end of LibreOfficeKit pre-initialization to enable * various optimizations. * - * Its function is to annotate a section @start = true - * to end (@start = false) via. two calls. Inside this + * Its function is to annotate a section @mode = 1 + * to end (@mode = 0) via. two calls. Inside this * section string allocators are replaced with ones which cause the * strings to be staticized at the end of the section. * @@ -313,6 +313,8 @@ SAL_DLLPUBLIC void SAL_CALL rtl_cache_free ( * This method is not thread-safe, nor intended for use in * a threaded context, cf. previous constraints. * + * Further @mode values may have further meanings, which may change at any time. + * * It is almost certainly not the method that you want, * use with extraordinary care referring to the * implementation. @@ -320,7 +322,7 @@ SAL_DLLPUBLIC void SAL_CALL rtl_cache_free ( * @since LibreOffice 6.1 */ SAL_DLLPUBLIC void SAL_CALL rtl_alloc_preInit ( - sal_Bool start + sal_uInt8 mode ) SAL_THROW_EXTERN_C(); /** @endcond */ diff --git a/sal/CppunitTest_sal_osl_file_details.mk b/sal/CppunitTest_sal_osl_file_details.mk new file mode 100644 index 000000000000..921ec37cd4d1 --- /dev/null +++ b/sal/CppunitTest_sal_osl_file_details.mk @@ -0,0 +1,65 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,sal_osl_file_details)) + +$(eval $(call gb_CppunitTest_add_exception_objects,sal_osl_file_details, \ + sal/qa/osl/file/forbidden \ +)) + +$(eval $(call gb_CppunitTest_add_libs,sal_osl_file_details, \ + $(if $(filter LINUX,$(OS)), \ + -ldl \ + -lrt \ + ) \ + $(if $(filter SOLARIS,$(OS)), \ + -lnsl \ + -lsocket \ + ) \ + $(if $(filter HAIKU,$(OS)), \ + -lnetwork \ + ) \ + $(BACKTRACE_LIBS) \ +)) + +$(eval $(call gb_CppunitTest_set_include,sal_osl_file_details, \ + $$(INCLUDE) \ + -I$(SRCDIR)/sal/inc \ +)) + +$(eval $(call gb_CppunitTest_use_externals,sal_osl_file_details, \ + dtoa \ + zlib \ +)) + +$(eval $(call gb_CppunitTest_use_library_objects,sal_osl_file_details,sal)) + +ifeq ($(OS),MACOSX) +$(eval $(call gb_CppunitTest_use_system_darwin_frameworks,sal_osl_file_details, \ + Carbon \ + CoreFoundation \ + Foundation \ + $(if $(ENABLE_MACOSX_SANDBOX),Security) \ +)) +endif + +$(eval $(call gb_CppunitTest_use_system_win32_libs,sal_osl_file_details, \ + advapi32 \ + comdlg32 \ + dbghelp \ + mpr \ + ole32 \ + shell32 \ + user32 \ + userenv \ + wer \ + ws2_32 \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/sal/Library_sal.mk b/sal/Library_sal.mk index a7026e8327d8..94e7fe10c5ca 100644 --- a/sal/Library_sal.mk +++ b/sal/Library_sal.mk @@ -109,6 +109,7 @@ $(eval $(call gb_Library_add_exception_objects,sal,\ sal/rtl/hash \ sal/rtl/locale \ sal/rtl/math \ + sal/rtl/preinit \ sal/rtl/random \ sal/rtl/rtl_process \ sal/rtl/strbuf \ diff --git a/sal/Module_sal.mk b/sal/Module_sal.mk index 854d46a8e6d4..242dc3297c31 100644 --- a/sal/Module_sal.mk +++ b/sal/Module_sal.mk @@ -31,6 +31,7 @@ $(eval $(call gb_Module_add_check_targets,sal,\ $(if $(filter WNT,$(OS)),CppunitTest_sal_retry_if_failed) \ CppunitTest_sal_osl_security \ CppunitTest_sal_osl \ + CppunitTest_sal_osl_file_details \ CppunitTest_sal_rtl \ CppunitTest_sal_types \ $(if $(COM_IS_CLANG),$(if $(COMPILER_EXTERNAL_TOOL)$(COMPILER_PLUGIN_TOOL),, \ diff --git a/sal/inc/setallowedpaths.hxx b/sal/inc/setallowedpaths.hxx new file mode 100644 index 000000000000..110b7cc1a46d --- /dev/null +++ b/sal/inc/setallowedpaths.hxx @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#if defined UNX +/** Resets the list of paths and associated permissions for osl + + @param[in] aPaths + Contains a ':' delimited list of control strings and paths. + Control segments are a short path that refers to the following + segments and contain either: + + r: read-only paths follow (the default) + w: read & write paths follow + x: executable paths follow + + Any real paths (ie. having resolved all symlinks) + accessed outside of these paths will cause an + osl_File_E_ACCESS error in the relevant method calls. + + This method is Unix specific. + + @see isForbidden in sal/osl/unx/file_impl.hxx +*/ +void setAllowedPaths(std::u16string_view aPaths); +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/osl/unx/file.cxx b/sal/osl/unx/file.cxx index ae1e4cdd876e..0243a172d915 100644 --- a/sal/osl/unx/file.cxx +++ b/sal/osl/unx/file.cxx @@ -24,6 +24,9 @@ #include <osl/detail/file.h> #include <rtl/byteseq.h> #include <rtl/string.hxx> +#include <o3tl/string_view.hxx> + +#include <setallowedpaths.hxx> #include "system.hxx" #include "createfilehandlefromfd.hxx" @@ -62,9 +65,10 @@ #include <android/log.h> #include <android/asset_manager.h> #include <o3tl/string_view.hxx> -#include <vector> #endif +#include <vector> + #ifdef LINUX #include <sys/vfs.h> // As documented by the kernel @@ -825,6 +829,140 @@ 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; + +static OString getParentFolder(std::string_view rFilePath) +{ + sal_Int32 n = rFilePath.find_last_of('/'); + OString folderPath; + if (n < 1) + folderPath = "."_ostr; + else + folderPath = OString(rFilePath.substr(0, n)); + + return folderPath; +} + +void setAllowedPaths( + std::u16string_view aPaths + ) +{ + allowedPathsRead.clear(); + allowedPathsReadWrite.clear(); + allowedPathsExecute.clear(); + + char eType = 'r'; + sal_Int32 nIndex = 0; + do + { + OString aPath = rtl::OUStringToOString( + o3tl::getToken(aPaths, 0, ':', nIndex), + RTL_TEXTENCODING_UTF8); + + if (aPath.getLength() == 0) + continue; + + if (aPath.getLength() == 1) + { + eType = aPath[0]; + continue; + } + + char resolvedPath[PATH_MAX]; + bool isResolved = !!realpath(aPath.getStr(), resolvedPath); + bool notExists = !isResolved && errno == ENOENT; + + if (notExists) + { + sal_Int32 n = aPath.lastIndexOf('/'); + OString folderPath = getParentFolder(aPath); + isResolved = !!realpath(folderPath.getStr(), resolvedPath); + notExists = !isResolved && errno == ENOENT; + + if (notExists || !isResolved || strlen(resolvedPath) + aPath.getLength() - n + 1 >= PATH_MAX) + return; // too bad + else + strcat(resolvedPath, aPath.getStr() + n); + } + + if (isResolved) + { + OString aPushPath(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, sal_uInt32 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 = getParentFolder(filePath); + + bool isResolved = !!realpath(folderPath.getStr(), resolvedPath); + bool notExists = !isResolved && errno == ENOENT; + if (notExists) // folder doesn't exist, check parent, in the end of chain checks "." + return isForbidden(folderPath, nFlags); + else if (!isResolved || 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; +} + #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 ) @@ -1032,6 +1170,10 @@ oslFileError openFilePath(const OString& filePath, oslFileHandle* pHandle, // set close-on-exec by default flags |= O_CLOEXEC; + // Sandboxing hook + if (isForbidden( filePath, uFlags )) + return osl_File_E_ACCES; + /* open the file */ int fd = open_c( filePath, flags, mode ); if (fd == -1) @@ -1650,4 +1792,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..22e78f00393f 100644 --- a/sal/osl/unx/file_impl.hxx +++ b/sal/osl/unx/file_impl.hxx @@ -40,6 +40,25 @@ struct DirectoryItem_Impl oslFileType getFileType() const; }; +/** + Determine if the passed in path is not contained inside + the allowed paths, or if no allowed paths are set it + will not forbid any access. + + @param[in] filePath + A URL (or path) that we want to check if it is forbidden + to access. + + @param[in] nFlags + Flags to determine what type of access is requested, + osl_File_OpenFlag_Read | Write, or 0x80 for 'execute'. + + This method is Unix specific. + + @see setAllowedPaths in sal/inc/setallowedpaths.hxx +*/ +bool isForbidden(const OString &filePath, sal_uInt32 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 566f5c87a65f..500a26a042b2 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, 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 ) @@ -547,6 +559,9 @@ oslFileError SAL_CALL osl_createDirectoryPath( osl::systemPathRemoveSeparator(sys_path); + if (isForbidden(sys_path, osl_File_OpenFlag_Create)) + return osl_File_E_ACCES; + return create_dir_recursively_(sys_path.pData->buffer, aDirectoryCreationCallbackFunc, pData); } @@ -579,6 +594,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 ); @@ -592,6 +611,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; @@ -668,6 +691,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); } @@ -723,6 +749,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) @@ -750,6 +780,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 c9c8cc31c705..daeb0e65572c 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> @@ -206,6 +207,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, 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 13320263c65a..0398afc58e45 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 f756d55bec42..b769b0ae90b3 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/forbidden.cxx b/sal/qa/osl/file/forbidden.cxx new file mode 100644 index 000000000000..d474fbbc39db --- /dev/null +++ b/sal/qa/osl/file/forbidden.cxx @@ -0,0 +1,254 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <cppunit/plugin/TestPlugIn.h> + +#if (defined UNX) + +#include <stdio.h> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <osl/file.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +#include <setallowedpaths.hxx> + +#include "../../../osl/unx/file_impl.hxx" + +using namespace osl; + +namespace osl::qa { + +static bool isForbidden(OUString const & path, sal_uInt32 flags) { + OString utf8; + auto const ok = path.convertToString(&utf8, RTL_TEXTENCODING_UTF8, RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR); + CPPUNIT_ASSERT(ok); + return ::isForbidden(utf8, flags); +} + +} + +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 + { + setAllowedPaths(u""); + OUString aBadURL = aTmpName3 + "/bad"; + OUString aGoodURL = aTmpName3 + "/good"; + deleteTestDirectory(aBadURL); + deleteTestDirectory(aGoodURL); + deleteTestDirectory(aTmpName3); + } + + void forbidden() + { + setAllowedPaths(maScratchGood); + + // check some corner cases first + CPPUNIT_ASSERT_EQUAL_MESSAGE("read bad should be forbidden", + true, osl::qa::isForbidden(".", osl_File_OpenFlag_Read)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("read bad should be forbidden", + true, osl::qa::isForbidden("", osl_File_OpenFlag_Read)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("read bad should be forbidden", + true, osl::qa::isForbidden("a", osl_File_OpenFlag_Read)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("read bad should be forbidden", + true, osl::qa::isForbidden("/", osl_File_OpenFlag_Read)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("read from non-existent should be allowed", + false, osl::qa::isForbidden(maScratchGood + "/notthere/file", osl_File_OpenFlag_Read)); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("read bad should be forbidden", + true, osl::qa::isForbidden(maScratchBad, osl_File_OpenFlag_Read)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("read from good should be allowed", + false, osl::qa::isForbidden(maScratchGood, osl_File_OpenFlag_Read)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("write to good should be forbidden", + true, osl::qa::isForbidden(maScratchGood, osl_File_OpenFlag_Write)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("create in good should be forbidden", + true, osl::qa::isForbidden(maScratchGood, osl_File_OpenFlag_Create)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("exec from good should be forbidden", + true, osl::qa::isForbidden(maScratchGood, 0x80)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("write to non-existent should be forbidden", + true, osl::qa::isForbidden(maScratchBad + "/notthere", osl_File_OpenFlag_Write)); + + setAllowedPaths(Concat2View("w:" + maScratchGood + ":x:" + maScratchBad)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("read bad should be forbidden", + true, osl::qa::isForbidden(maScratchBad, osl_File_OpenFlag_Read)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("read from good should be allowed", // w implies 'r' + false, osl::qa::isForbidden(maScratchGood, osl_File_OpenFlag_Read)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("write to good should be allowed", + false, osl::qa::isForbidden(maScratchGood, osl_File_OpenFlag_Write)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("exec from good should be forbidden", + true, osl::qa::isForbidden(maScratchGood, 0x80)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("exec from bad should be allowed", + false, osl::qa::isForbidden(maScratchBad, 0x80)); + + setAllowedPaths(Concat2View(":r:" + maScratchBad)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("write to non-existent should be forbidden", + true, osl::qa::isForbidden(maScratchGood + "/notthere", osl_File_OpenFlag_Write)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("write to non-existent should be forbidden 2", + true, osl::qa::isForbidden(maScratchGood + "/notthere/file", osl_File_OpenFlag_Write)); + + setAllowedPaths(Concat2View(":r:" + maScratchBad + ":w:" + maScratchGood + "/notthere")); + CPPUNIT_ASSERT_EQUAL_MESSAGE("write to non-existent should be allowed", + false, osl::qa::isForbidden(maScratchGood + "/notthere", osl_File_OpenFlag_Write)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("write to non-existent should be allowed 2", + false, osl::qa::isForbidden(maScratchGood + "/notthere/file", osl_File_OpenFlag_Write)); + } + +/* + void open() + { + 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() + { + 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(); + + //TODO: The following parts are mainly copied from sal/qa/osl/file/osl_File.cxx and + // sal/qa/osl/file/osl_File_Const.h (but the latter cannot be included directly, as its use of + // static C++ objects would cause issues with the use of gb_CppunitTest_use_library_objects in + // this test's sal/CppunitTest_sal_osl_file_details.mk): + private: + static OUString getTempDirectoryURL_() + { + OUString aDir; + CPPUNIT_ASSERT_EQUAL_MESSAGE("couldn't get system temp URL", + osl::FileBase::E_None, osl::FileBase::getTempDirURL(aDir)); + // This resolves symlinks in the temp path if any + CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, + osl::FileBase::getAbsoluteFileURL(aDir, aDir, aDir)); + return aDir; + } + + OUString aTempDirectoryURL; + + OUString aTmpName3; + + /** simple version to judge if a file name or directory name is a URL or a system path, just to see if it + is start with "file:///";. + */ + static bool isURL(const OUString& pathname) + { + return pathname.startsWith(u"file:///"); + } + + /** create a temp test directory using OUString name of full qualified URL or system path. + */ + static void createTestDirectory(const OUString& dirname) + { + OUString aPathURL = dirname.copy(0); + osl::FileBase::RC nError; + + if (!isURL(dirname)) + osl::FileBase::getFileURLFromSystemPath(dirname, aPathURL); // convert if not full qualified URL + nError = Directory::create(aPathURL); + if ((nError != osl::FileBase::E_None) && (nError != osl::FileBase::E_EXIST)) + printf("createTestDirectory failed: %d! ", int(nError)); + } + + /** delete a temp test directory using OUString name of full qualified URL or system path. + */ + static void deleteTestDirectory(const OUString& dirname) + { + OUString aPathURL = dirname.copy(0); + if (!isURL(dirname)) + osl::FileBase::getFileURLFromSystemPath(dirname, aPathURL); // convert if not full qualified URL + + Directory testDir(aPathURL); + if (testDir.isOpen()) + testDir.close(); // close if still open. + + osl::FileBase::RC nError = Directory::remove(aPathURL); + + OString strError = "In deleteTestDirectory function: remove Directory " + + OUStringToOString(aPathURL, RTL_TEXTENCODING_ASCII_US) + " -> result: " + OString::number(nError); + CPPUNIT_ASSERT_MESSAGE(strError.getStr(), (osl::FileBase::E_None == nError) || (nError == osl::FileBase::E_NOENT)); + } + + public: + Forbidden(): + aTempDirectoryURL(getTempDirectoryURL_()), + aTmpName3( aTempDirectoryURL + "/tmpdir" ) + {} + }; + + CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(osl_Forbidden::Forbidden, "osl_Forbidden"); + + CPPUNIT_REGISTRY_ADD_TO_DEFAULT("osl_Forbidden"); +} +#endif + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/qa/rtl/alloc/rtl_alloc.cxx b/sal/qa/rtl/alloc/rtl_alloc.cxx index 0e743cae3c11..f63d3f1275aa 100644 --- a/sal/qa/rtl/alloc/rtl_alloc.cxx +++ b/sal/qa/rtl/alloc/rtl_alloc.cxx @@ -157,7 +157,7 @@ public: const char sample[] = "Hello World"; std::vector<OUString> aStrings; - rtl_alloc_preInit(true); + rtl_alloc_preInit(1); OUString aFoo(o3tl::nonStaticString(u"foo")); @@ -182,7 +182,7 @@ public: } // should static-ize all the strings. - rtl_alloc_preInit(false); + rtl_alloc_preInit(0); for (size_t i = 0; i < aStrings.size(); ++i) CPPUNIT_ASSERT_MESSAGE( "static after.", (aStrings[i].pData->refCount & SAL_STRING_STATIC_FLAG) ); diff --git a/sal/rtl/preinit.cxx b/sal/rtl/preinit.cxx new file mode 100644 index 000000000000..d1c6c1478d5a --- /dev/null +++ b/sal/rtl/preinit.cxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <stdlib.h> +#include <string.h> + +#include <rtl/alloc.h> +#include <rtl/textenc.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/types.h> + +#include <setallowedpaths.hxx> + +#include "strimp.hxx" + +void SAL_CALL rtl_alloc_preInit(sal_uInt8 mode) SAL_THROW_EXTERN_C() +{ + switch (mode) + { + case 0: + alloc_preInit(false); + break; + case 1: + alloc_preInit(true); + break; +#if defined UNX + case 2: + { + const char* pAllowedPaths = getenv("SAL_ALLOWED_PATHS"); + if (pAllowedPaths) + setAllowedPaths( + OUString(pAllowedPaths, strlen(pAllowedPaths), RTL_TEXTENCODING_UTF8)); + break; + } +#endif + default: + SAL_WARN("sal.rtl", "Unknown rtl_alloc_preInit mode " << unsigned(mode)); + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sal/rtl/strimp.cxx b/sal/rtl/strimp.cxx index 678bbb69c406..fac73cecf7b4 100644 --- a/sal/rtl/strimp.cxx +++ b/sal/rtl/strimp.cxx @@ -65,7 +65,7 @@ static void mark_static(void *addr, sal_Size /* size */) str->refCount |= SAL_STRING_STATIC_FLAG; } -void SAL_CALL rtl_alloc_preInit (sal_Bool start) noexcept +void alloc_preInit (bool start) noexcept { if (start) { diff --git a/sal/rtl/strimp.hxx b/sal/rtl/strimp.hxx index 5fd759313ed3..6691296aa3b8 100644 --- a/sal/rtl/strimp.hxx +++ b/sal/rtl/strimp.hxx @@ -60,6 +60,8 @@ typedef void (*rtl_freeStringFn)(void *); extern rtl_allocateStringFn rtl_allocateString; extern rtl_freeStringFn rtl_freeString; +void alloc_preInit(bool start) noexcept; + // string lifetime instrumentation / diagnostics #if USE_SDT_PROBES # define PROBE_SNAME(n,b) n ## _ ## b diff --git a/solenv/clang-format/excludelist b/solenv/clang-format/excludelist index 118104bfd9ff..03477616dd66 100644 --- a/solenv/clang-format/excludelist +++ b/solenv/clang-format/excludelist @@ -7432,6 +7432,7 @@ sal/qa/OStringBuffer/rtl_OStringBuffer.cxx sal/qa/OStringBuffer/rtl_String_Const.h sal/qa/inc/valueequal.hxx sal/qa/osl/condition/osl_Condition.cxx +sal/qa/osl/file/forbidden.cxx sal/qa/osl/file/osl_File.cxx sal/qa/osl/file/osl_File_Const.h sal/qa/osl/file/osl_old_test_file.cxx