Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
[release/3.1] Single-File: Unix: Extract to users-specific temp direc…
Browse files Browse the repository at this point in the history
…tory (#9011)

** Issue

dotnet/runtime#3846

** Customer Scenario

On some Unix systems, multiple users cannot use single-file apps on the same machine.

** Problem

In .net core 3, single file apps run by extracting the bundled contents to a temp directory.
The extraction directory is machine specific, and can be set through DOTNET_BUNDLE_EXTRACT_BASE_DIR environment variable.

When this setting is not configured, the host tries to use certain default directories.
On windows, extraction is within %TMPDIR%, which is user specific.
On Unix systems $TMPDIR/.net if set, which may be user specific (ex: MAC)
Otherwise, the extraction directory is within /var/tmp/ or /tmp/ which is common to all users, and may be locked by a specific user on first creation.

** Solution

This change fixes this issue by defaulting the extraction base directory in Unix systems to
<temp-dir>/.net/<user-ID> , where
<temp-dir>/.net/ has permission 0777, and
<temp-dir>/.net/<user-ID>/ has permission 0700.

** Risk

Low, scenario is contained, change is small.

** Master Branch

dotnet/runtime#2329
  • Loading branch information
swaroop-sridhar authored Mar 25, 2020
1 parent 8571087 commit ce27e0e
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 6 deletions.
10 changes: 4 additions & 6 deletions src/corehost/cli/apphost/bundle/bundle_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,22 +169,20 @@ void bundle_runner_t::reopen_host_for_reading()
// Compute the final extraction location as:
// m_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/<id>/...
//
// If DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set in the environment, the
// base directory defaults to $TMPDIR/.net
// If DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set in the environment,
// a default is choosen within the temporary directory.
void bundle_runner_t::determine_extraction_dir()
{
if (!pal::getenv(_X("DOTNET_BUNDLE_EXTRACT_BASE_DIR"), &m_extraction_dir))
{
if (!pal::get_temp_directory(m_extraction_dir))
if (!pal::get_default_bundle_extraction_base_dir(m_extraction_dir))
{
trace::error(_X("Failure processing application bundle."));
trace::error(_X("Failed to determine location for extracting embedded files"));
trace::error(_X("DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set, and temp-directory doesn't exist or is not readable/writable."));
trace::error(_X("DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set, and a read-write temp-directory couldn't be created."));

throw StatusCode::BundleExtractionFailure;
}

append_path(&m_extraction_dir, _X(".net"));
}

pal::string_t host_name = strip_executable_ext(get_filename(m_bundle_path));
Expand Down
4 changes: 4 additions & 0 deletions src/corehost/common/pal.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ namespace pal

bool get_temp_directory(pal::string_t& tmp_dir);

// Returns a platform-specific, user-private directory within get_temp_directory()
// that can be used for extracting out components of a single-file app.
bool get_default_bundle_extraction_base_dir(string_t& extraction_dir);

int xtoi(const char_t* input);

bool get_loaded_library(const char_t *library_name, const char *symbol_name, /*out*/ dll_t *dll, /*out*/ pal::string_t *path);
Expand Down
50 changes: 50 additions & 0 deletions src/corehost/common/pal.unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <fcntl.h>
#include <fnmatch.h>
#include <ctime>
#include <pwd.h>

#if defined(__APPLE__)
#include <mach-o/dyld.h>
Expand Down Expand Up @@ -299,6 +300,55 @@ bool pal::get_temp_directory(pal::string_t& tmp_dir)
return false;
}

bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir)
{
if (!get_temp_directory(extraction_dir))
{
return false;
}

append_path(&extraction_dir, _X(".net"));
pal::string_t dotnetdir(extraction_dir);

// getuid() is the real user ID, and the call has no defined errors.
struct passwd* passwd = getpwuid(getuid());
if (passwd == nullptr || passwd->pw_name == nullptr)
{
return false;
}

append_path(&extraction_dir, passwd->pw_name);

if (is_read_write_able_directory(extraction_dir))
{
return true;
}

// Create $TMPDIR/.net accessible to everyone
if (::mkdir(dotnetdir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0)
{
// In the above mkdir() system call, some permissions are strangely dropped!
// Linux drops S_IWO and Mac drops S_IWG | S_IWO.
// So these are again explicitly set by calling chmod()
if (chmod(dotnetdir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) != 0)
{
return false;
}
}
else if (errno != EEXIST)
{
return false;
}

// Create $TMPDIR/.net/username accessible only to the user
if (::mkdir(extraction_dir.c_str(), S_IRWXU | S_ISVTX) != 0 && errno != EEXIST)
{
return false;
}

return is_read_write_able_directory(extraction_dir);
}

bool pal::get_global_dotnet_dirs(std::vector<pal::string_t>* recv)
{
// No support for global directories in Unix.
Expand Down
25 changes: 25 additions & 0 deletions src/corehost/common/pal.windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,31 @@ bool pal::get_temp_directory(pal::string_t& tmp_dir)
return pal::realpath(&tmp_dir);
}

bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir)
{
if (!get_temp_directory(extraction_dir))
{
return false;
}

append_path(&extraction_dir, _X(".net"));
// Windows Temp-Path is already user-private.

if (realpath(&extraction_dir))
{
return true;
}

// Create the %TEMP%\.net directory
if (CreateDirectoryW(extraction_dir.c_str(), NULL) == 0 &&
GetLastError() != ERROR_ALREADY_EXISTS)
{
return false;
}

return realpath(&extraction_dir);
}

static bool wchar_convert_helper(DWORD code_page, const char* cstr, int len, pal::string_t* out)
{
out->clear();
Expand Down

0 comments on commit ce27e0e

Please sign in to comment.