dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.19k stars 4.72k forks source link

Single file executable extraction path not writable when setuid bit is used #61458

Open bobvandevijver opened 2 years ago

bobvandevijver commented 2 years ago

Description

When you have a single file dotnet core application, it will extract itself to a directory. The location can be influenced with the DOTNET_BUNDLE_EXTRACT_BASE_DIR, but by default it seems to use /home/<user>/.net/<app>.

However, when the setuid bit has been set to execute the application as another user, the extraction path user is not adjusted accordingly. This causes the execution of the application to fail, as it cannot create the directory due to insufficient rights.

Reproduction Steps

Build a self-contained "Hello World" app, with the following properties:

<Project Sdk="Microsoft.NET.Sdk">
    <Import Project="..\default.props" />
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <PublishSingleFile>true</PublishSingleFile>
        <SelfContained>true</SelfContained>
        <RuntimeIdentifier>linux-musl-x64</RuntimeIdentifier>
        <InvariantGlobalization>true</InvariantGlobalization>
    </PropertyGroup>
    <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
        <DebugType>none</DebugType>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    </PropertyGroup>
</Project>

Compile with dotnet publish -c Release <your-app> -r <your-rid>

Expected behavior

I expect that the directory used for extracting is based on the user after the setuid bit has been "applied", and not the user that has initially started the execution.

In the example under "Actual behaviour" the extraction should happen in /home/appuser/.net/App/ as the user running the application is appuser due to the setuid bit, and not the user (bobv) that is initially invoking the account.

Actual behavior

XPS15-BOB:/opt/nis$ ls -la
total 78220
drwxr-xr-x    2 bobv     root          4096 Nov 11 09:47 .
drwxr-xr-x    3 root     root          4096 Nov 11 09:45 ..
-rwsr-xr-x    1 appuser      bobv      80085655 Nov 11 09:45 App
XPS15-BOB:/opt/nis$ ./App
Failure processing application bundle.
Failed to create directory [/home/bobv/.net/App/] for extracting bundled files
A fatal error was encountered. Could not extract contents of the bundle

Regression?

No response

Known Workarounds

Set the DOTNET_BUNDLE_EXTRACT_BASE_DIR environment variable to a path that can be written by the user actually running the app.

Configuration

I'm using dotnet 6.0.100 to publish the file, which targets netcoreapp3.1. I cannot upgrade because I need to support RHEL6, as the lab equipment this needs to run on cannot be upgraded.

I have confirmed this using the following rids:

Other information

This method retrieves the directory:

https://github.com/dotnet/runtime/blob/6f5de0b2b979a70e6fe36904d0d4f087c32f9c7e/src/native/corehost/bundle/extractor.cpp#L38

Here is uses the HOME env var, which is indeed the wrong home directory when the setuid bit has been set:

https://github.com/dotnet/runtime/blob/c88c88a5f07325a70322cfc056949e8d52e4a04f/src/native/corehost/hostmisc/pal.unix.cpp#L342

Although I'm not sure why the read/write check passes (or at least, I think it is).

ghost commented 2 years ago

Tagging subscribers to this area: @agocke, @vitek-karas, @vsadov See info in area-owners.md if you want to be subscribed.

Issue Details
### Description When you have a single file dotnet core application, it will extract itself to a directory. The location can be influenced with the `DOTNET_BUNDLE_EXTRACT_BASE_DIR`, but by default it seems to use `/home//.net/`. However, when the `setuid` bit has been set to execute the application as another user, the extraction path user is not adjusted accordingly. This causes the execution of the application to fail, as it cannot create the directory due to insufficient rights. ### Reproduction Steps Build a self-contained "Hello World" app, with the following properties: ``` Exe netcoreapp3.1 true true linux-musl-x64 true none true ``` Compile with `dotnet publish -c Release -r ` ### Expected behavior I expect that the directory used for extracting is based on the user after the `setuid` bit has been "applied", and not the user that has initially started the execution. In the example under "Actual behaviour" the extraction should happen in `/home/appuser/.net/App/` as the user running the application is `appuser` due to the `setuid` bit, and not the user (`bobv`) that is initially invoking the account. ### Actual behavior ``` XPS15-BOB:/opt/nis$ ls -la total 78220 drwxr-xr-x 2 bobv root 4096 Nov 11 09:47 . drwxr-xr-x 3 root root 4096 Nov 11 09:45 .. -rwsr-xr-x 1 appuser bobv 80085655 Nov 11 09:45 App XPS15-BOB:/opt/nis$ ./App Failure processing application bundle. Failed to create directory [/home/bobv/.net/App/] for extracting bundled files A fatal error was encountered. Could not extract contents of the bundle ``` ### Regression? _No response_ ### Known Workarounds Set the `DOTNET_BUNDLE_EXTRACT_BASE_DIR` environment variable to a path that can be written by the user actually running the app. ### Configuration I'm using dotnet 6.0.100 to publish the file, which targets `netcoreapp3.1`. I cannot upgrade because I need to support RHEL6, as the lab equipment this needs to run on cannot be upgraded. I have confirmed this using the following rids: - rhel.6-x64 (yes, I know, nothing I can do about this unfortunately) - rhel.7-x64 - linux-musl-x64 - linux-x64 (possibly, couldn't test this completely as it triggers a BSOD on my WSL1 Debian) ### Other information This method retrieves the directory: https://github.com/dotnet/runtime/blob/6f5de0b2b979a70e6fe36904d0d4f087c32f9c7e/src/native/corehost/bundle/extractor.cpp#L38 Here is uses the `HOME` env var, which is indeed the wrong home directory when the `setuid` bit has been set: https://github.com/dotnet/runtime/blob/c88c88a5f07325a70322cfc056949e8d52e4a04f/src/native/corehost/hostmisc/pal.unix.cpp#L342 Although I'm not sure why the read/write check passes (or at least, I think it is).
Author: bobvandevijver
Assignees: -
Labels: `area-Single-File`, `untriaged`
Milestone: -
vitek-karas commented 2 years ago

You can enable tracing:

export COREHOST_TRACE=1
export COREHOST_TRACEFILE=host.txt

Repro the failure and see host.txt in current directory. It should have more details on why it picked a certain folder. Since the app is targeting 3.1 it should be using the .NET Core 3.1 host. So the code for that is actually here: https://github.com/dotnet/core-setup/blob/29be638bf4b745d1356a8dc45846ca46deda955a/src/corehost/common/pal.unix.cpp#L275-L310

Recently there's an added capability to read the "home" from the getpwuid function if HOME is not defined. But that doesn't seem to be the case here. Maybe we could improve on this and if the HOME exists and it's not writable, use the one from getpwuid...

bobvandevijver commented 2 years ago

Here's the output!

Tracing enabled @ Thu Nov 11 11:10:23 2021 UTC
--- Invoked apphost [version: 3.1.21, commit hash: df8abc0f7ea6a8add9cdb23adc8b18673a329df8] main = {
./App
}
The managed DLL bound to this executable is: 'App.dll'
Files embedded within the bundled will be extracted to [/home/bobv/.net/App/OSmwyoj0Foxb46lYJfUDpkSHLcKSsrE=] directory
Failure processing application bundle.
Failed to create directory [/home/bobv/.net/App/] for extracting bundled files
A fatal error was encountered. Could not extract contents of the bundle
vitek-karas commented 2 years ago

I think that matches what you described in your analysis of the problem. I don't know either why the read/write check on HOME seems to succeed though.