Strict temporary image file permissions (#6358)

* Write temp images to private tmp directory (Linux)

The directory is in /tmp with 700 permissions.

* Reduce temp image file permissions in Linux

Set temporary image file permissions to read/write for the user only.

* Use private tmp directory for temp images in MacOS

* Use private tmp directory for temp images Windows

* Use GLib to create temporary directories

* Reuse temp directory if possible
This commit is contained in:
Lawrence37 2023-01-02 12:32:15 -08:00 committed by GitHub
parent 8d29d361a8
commit 57c1822b2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 358 additions and 1 deletions

124
rtengine/winutils.h Normal file
View File

@ -0,0 +1,124 @@
/*
* This file is part of RawTherapee.
*
* Copyright (c) 2021 Lawrence Lee
*
* RawTherapee is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RawTherapee is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RawTherapee. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#ifdef WIN32
#include <aclapi.h>
#include <windows.h>
#include "noncopyable.h"
/**
* Wrapper for pointers to memory allocated by HeapAlloc.
*
* Memory is automatically freed when the object goes out of scope.
*/
template <typename T>
class WinHeapPtr : public rtengine::NonCopyable
{
private:
const T ptr;
public:
WinHeapPtr() = delete;
/** Allocates the specified number of bytes in the process heap. */
explicit WinHeapPtr(SIZE_T bytes): ptr(static_cast<T>(HeapAlloc(GetProcessHeap(), 0, bytes))) {};
~WinHeapPtr()
{
// HeapFree does a null check.
HeapFree(GetProcessHeap(), 0, static_cast<LPVOID>(ptr));
}
T operator ->() const
{
return ptr;
}
operator T() const
{
return ptr;
}
};
/**
* Wrapper for HLOCAL pointers to memory allocated by LocalAlloc.
*
* Memory is automatically freed when the object goes out of scope.
*/
template <typename T>
class WinLocalPtr : public rtengine::NonCopyable
{
private:
const T ptr;
public:
WinLocalPtr() = delete;
/** Wraps a raw pointer. */
WinLocalPtr(T pointer): ptr(pointer) {};
~WinLocalPtr()
{
// LocalFree does a null check.
LocalFree(static_cast<HLOCAL>(ptr));
}
T operator ->() const
{
return ptr;
}
operator T() const
{
return ptr;
}
};
/**
* Wrapper for HANDLEs.
*
* Handles are automatically closed when the object goes out of scope.
*/
class WinHandle : public rtengine::NonCopyable
{
private:
const HANDLE handle;
public:
WinHandle() = delete;
/** Wraps a HANDLE. */
WinHandle(HANDLE handle): handle(handle) {};
~WinHandle()
{
CloseHandle(handle);
}
operator HANDLE() const
{
return handle;
}
};
#endif

View File

@ -44,6 +44,8 @@
#ifdef WIN32
#include "windows.h"
#include "../rtengine/winutils.h"
#endif
using namespace rtengine::procparams;
@ -134,6 +136,235 @@ bool find_default_monitor_profile (GdkWindow *rootwin, Glib::ustring &defprof, G
}
#endif
bool hasUserOnlyPermission(const Glib::ustring &dirname)
{
#if defined(__linux__) || defined(__APPLE__)
const Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(dirname);
const Glib::RefPtr<Gio::FileInfo> file_info = file->query_info("owner::user,unix::mode");
if (!file_info) {
return false;
}
const Glib::ustring owner = file_info->get_attribute_string("owner::user");
const guint32 mode = file_info->get_attribute_uint32("unix::mode");
return (mode & 0777) == 0700 && owner == Glib::get_user_name();
#elif defined(WIN32)
const Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(dirname);
const Glib::RefPtr<Gio::FileInfo> file_info = file->query_info("owner::user");
if (!file_info) {
return false;
}
// Current user must be the owner.
const Glib::ustring user_name = Glib::get_user_name();
const Glib::ustring owner = file_info->get_attribute_string("owner::user");
if (user_name != owner) {
return false;
}
// Get security descriptor and discretionary access control list.
PACL dacl = nullptr;
PSECURITY_DESCRIPTOR sec_desc_raw_ptr = nullptr;
auto win_error = GetNamedSecurityInfo(
dirname.c_str(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
nullptr,
nullptr,
&dacl,
nullptr,
&sec_desc_raw_ptr
);
const WinLocalPtr<PSECURITY_DESCRIPTOR> sec_desc_ptr(sec_desc_raw_ptr);
if (win_error != ERROR_SUCCESS) {
return false;
}
// Must not inherit permissions.
SECURITY_DESCRIPTOR_CONTROL sec_desc_control;
DWORD revision;
if (!(
GetSecurityDescriptorControl(sec_desc_ptr, &sec_desc_control, &revision)
&& sec_desc_control & SE_DACL_PROTECTED
)) {
return false;
}
// Check that there is one entry allowing full access.
ULONG acl_entry_count;
PEXPLICIT_ACCESS acl_entry_list_raw = nullptr;
win_error = GetExplicitEntriesFromAcl(dacl, &acl_entry_count, &acl_entry_list_raw);
const WinLocalPtr<PEXPLICIT_ACCESS> acl_entry_list(acl_entry_list_raw);
if (win_error != ERROR_SUCCESS || acl_entry_count != 1) {
return false;
}
const EXPLICIT_ACCESS &ace = acl_entry_list[0];
if (
ace.grfAccessMode != GRANT_ACCESS
|| (ace.grfAccessPermissions & FILE_ALL_ACCESS) != FILE_ALL_ACCESS
|| ace.Trustee.TrusteeForm != TRUSTEE_IS_SID // Should already be SID, but double check.
) {
return false;
}
// ACE must be for the current user.
HANDLE process_token_raw;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &process_token_raw)) {
return false;
}
const WinHandle process_token(process_token_raw);
DWORD actual_token_info_size = 0;
GetTokenInformation(process_token, TokenUser, nullptr, 0, &actual_token_info_size);
if (!actual_token_info_size) {
return false;
}
const WinHeapPtr<PTOKEN_USER> user_token_ptr(actual_token_info_size);
if (!user_token_ptr || !GetTokenInformation(
process_token,
TokenUser,
user_token_ptr,
actual_token_info_size,
&actual_token_info_size
)) {
return false;
}
return EqualSid(ace.Trustee.ptstrName, user_token_ptr->User.Sid);
#endif
return false;
}
/**
* Sets read and write permissions, and optionally the execute permission, for
* the user and no permissions for others.
*/
void setUserOnlyPermission(const Glib::RefPtr<Gio::File> file, bool execute)
{
#if defined(__linux__) || defined(__APPLE__)
const Glib::RefPtr<Gio::FileInfo> file_info = file->query_info("unix::mode");
if (!file_info) {
return;
}
guint32 mode = file_info->get_attribute_uint32("unix::mode");
mode = (mode & ~0777) | (execute ? 0700 : 0600);
try {
file->set_attribute_uint32("unix::mode", mode, Gio::FILE_QUERY_INFO_NONE);
} catch (Gio::Error &) {
}
#elif defined(WIN32)
// Get the current user's SID.
HANDLE process_token_raw;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &process_token_raw)) {
return;
}
const WinHandle process_token(process_token_raw);
DWORD actual_token_info_size = 0;
GetTokenInformation(process_token, TokenUser, nullptr, 0, &actual_token_info_size);
if (!actual_token_info_size) {
return;
}
const WinHeapPtr<PTOKEN_USER> user_token_ptr(actual_token_info_size);
if (!user_token_ptr || !GetTokenInformation(
process_token,
TokenUser,
user_token_ptr,
actual_token_info_size,
&actual_token_info_size
)) {
return;
}
const PSID user_sid = user_token_ptr->User.Sid;
// Get a handle to the file.
const Glib::ustring filename = file->get_path();
const HANDLE file_handle_raw = CreateFile(
filename.c_str(),
READ_CONTROL | WRITE_DAC,
0,
nullptr,
OPEN_EXISTING,
execute ? FILE_FLAG_BACKUP_SEMANTICS : FILE_ATTRIBUTE_NORMAL,
nullptr
);
if (file_handle_raw == INVALID_HANDLE_VALUE) {
return;
}
const WinHandle file_handle(file_handle_raw);
// Create the user-only permission and set it.
EXPLICIT_ACCESS ea = {
.grfAccessPermissions = FILE_ALL_ACCESS,
.grfAccessMode = GRANT_ACCESS,
.grfInheritance = NO_INHERITANCE,
};
BuildTrusteeWithSid(&(ea.Trustee), user_sid);
PACL new_dacl_raw = nullptr;
auto win_error = SetEntriesInAcl(1, &ea, nullptr, &new_dacl_raw);
if (win_error != ERROR_SUCCESS) {
return;
}
const WinLocalPtr<PACL> new_dacl(new_dacl_raw);
SetSecurityInfo(
file_handle,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
nullptr,
nullptr,
new_dacl,
nullptr
);
#endif
}
/**
* Gets the path to the temp directory, creating it if necessary.
*/
Glib::ustring getTmpDirectory()
{
#if defined(__linux__) || defined(__APPLE__) || defined(WIN32)
static Glib::ustring recent_dir = "";
const Glib::ustring tmp_dir_root = Glib::get_tmp_dir();
const Glib::ustring subdir_base =
Glib::ustring::compose("rawtherapee-%1", Glib::get_user_name());
Glib::ustring dir = Glib::build_filename(tmp_dir_root, subdir_base);
// Returns true if the directory doesn't exist or has the right permissions.
auto is_usable_dir = [](const Glib::ustring &dir_path) {
return !Glib::file_test(dir_path, Glib::FILE_TEST_EXISTS) || (Glib::file_test(dir_path, Glib::FILE_TEST_IS_DIR) && hasUserOnlyPermission(dir_path));
};
if (!(is_usable_dir(dir) || recent_dir.empty())) {
// Try to reuse the random suffix directory.
dir = recent_dir;
}
if (!is_usable_dir(dir)) {
// Create new directory with random suffix.
gchar *const rand_dir = g_dir_make_tmp((subdir_base + "-XXXXXX").c_str(), nullptr);
if (!rand_dir) {
return tmp_dir_root;
}
dir = recent_dir = rand_dir;
g_free(rand_dir);
Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(dir);
setUserOnlyPermission(file, true);
} else if (!Glib::file_test(dir, Glib::FILE_TEST_EXISTS)) {
// Create the directory.
Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(dir);
bool dir_created = file->make_directory();
if (!dir_created) {
return tmp_dir_root;
}
setUserOnlyPermission(file, true);
}
return dir;
#else
return Glib::get_tmp_dir();
#endif
}
}
class EditorPanel::ColorManagementToolbar
@ -2058,7 +2289,7 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector<rtengine::IImagefloat*> *p
dirname = options.editor_custom_out_dir;
break;
default: // Options::EDITOR_OUT_DIR_TEMP
dirname = Glib::get_tmp_dir();
dirname = getTmpDirectory();
break;
}
Glib::ustring fullFileName = Glib::build_filename(dirname, shortname);
@ -2119,6 +2350,8 @@ bool EditorPanel::idle_sentToGimp (ProgressConnector<int> *pc, rtengine::IImagef
parent->setProgress (0.);
bool success = false;
setUserOnlyPermission(Gio::File::create_for_path(filename), false);
if (options.editorToSendTo == 1) {
success = ExtProgStore::openInGimp (filename);
} else if (options.editorToSendTo == 2) {