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

Commit 5b8f55c

Browse files
committed
Fix access to user's home directory on Unix
Today in several places we get a user's home directory by accessing the HOME environment variable. This is the right place to check first, but in certain cases it may not be set, such as in initialization scripts. This commit implements a fallback, such that if HOME doesn't have a path, we consult the pw_dir path from a call to getpwuid_r for the current user. As long as I was adding getpwuid_r to the native shim, I also moved our existing geteuid and getegid imports to the shims.
1 parent ac7a7e8 commit 5b8f55c

File tree

17 files changed

+291
-29
lines changed

17 files changed

+291
-29
lines changed

src/Common/src/Interop/Unix/libc/Interop.getegid.cs renamed to src/Common/src/Interop/Unix/System.Native/Interop.GetEGid.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
internal static partial class Interop
88
{
9-
internal static partial class libc
9+
internal static partial class Sys
1010
{
11-
[DllImport(Libraries.Libc)]
12-
internal static extern int getegid();
11+
[DllImport(Libraries.SystemNative)]
12+
internal static extern int GetEGid();
1313
}
1414
}

src/Common/src/Interop/Unix/libc/Interop.geteuid.cs renamed to src/Common/src/Interop/Unix/System.Native/Interop.GetEUid.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
internal static partial class Interop
88
{
9-
internal static partial class libc
9+
internal static partial class Sys
1010
{
11-
[DllImport(Libraries.Libc)]
12-
internal static extern int geteuid();
11+
[DllImport(Libraries.SystemNative)]
12+
internal static extern int GetEUid();
1313
}
1414
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class Sys
10+
{
11+
internal unsafe struct Passwd
12+
{
13+
internal byte* Name;
14+
internal byte* Password;
15+
internal int UserId;
16+
internal int GroupId;
17+
internal byte* UserInfo;
18+
internal byte* HomeDirectory;
19+
internal byte* Shell;
20+
};
21+
22+
[DllImport(Libraries.SystemNative, SetLastError = true)]
23+
internal static extern unsafe int GetPwUid(int uid, out Passwd pwd, byte* buf, long bufLen, out IntPtr result);
24+
}
25+
}

src/Common/src/System/IO/PersistedFiles.Unix.cs

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Diagnostics;
5+
using System.Runtime.InteropServices;
56

67
namespace System.IO
78
{
@@ -81,8 +82,7 @@ internal static string GetUserFeatureDirectory(params string[] featurePathParts)
8182

8283
private static void EnsureUserDirectories()
8384
{
84-
// TODO (2820): Use getpwuid_r(geteuid(), ...) instead of the HOME environment variable
85-
string userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
85+
string userHomeDirectory = GetHomeDirectory();
8686

8787
if (string.IsNullOrEmpty(userHomeDirectory))
8888
{
@@ -94,5 +94,95 @@ private static void EnsureUserDirectories()
9494
TopLevelHiddenDirectory,
9595
SecondLevelDirectory);
9696
}
97+
98+
/// <summary>Gets the current user's home directory.</summary>
99+
/// <returns>The path to the home directory, or null if it could not be determined.</returns>
100+
internal static string GetHomeDirectory()
101+
{
102+
// First try to get the user's home directory from the HOME environment variable.
103+
// This should work in most cases.
104+
string userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
105+
if (!string.IsNullOrEmpty(userHomeDirectory))
106+
return userHomeDirectory;
107+
108+
// In initialization conditions, however, the "HOME" enviroment variable may
109+
// not yet be set. For such cases, consult with the password entry.
110+
unsafe
111+
{
112+
// First try with a buffer that should suffice for 99% of cases.
113+
// Note that, theoretically, userHomeDirectory may be null in the success case
114+
// if we simply couldn't find a home directory for the current user.
115+
// In that case, we pass back the null value and let the caller decide
116+
// what to do.
117+
const int BufLen = 1024;
118+
byte* stackBuf = stackalloc byte[BufLen];
119+
if (TryGetHomeDirectoryFromPasswd(stackBuf, BufLen, out userHomeDirectory))
120+
return userHomeDirectory;
121+
122+
// Fallback to heap allocations if necessary, growing the buffer until
123+
// we succeed. TryGetHomeDirectory will throw if there's an unexpected error.
124+
int lastBufLen = BufLen;
125+
while (true)
126+
{
127+
lastBufLen *= 2;
128+
byte[] heapBuf = new byte[lastBufLen];
129+
fixed (byte* buf = heapBuf)
130+
{
131+
if (TryGetHomeDirectoryFromPasswd(buf, heapBuf.Length, out userHomeDirectory))
132+
return userHomeDirectory;
133+
}
134+
}
135+
}
136+
}
137+
138+
/// <summary>Wrapper for getpwuid_r.</summary>
139+
/// <param name="buf">The scratch buffer to pass into getpwuid_r.</param>
140+
/// <param name="bufLen">The length of <paramref name="buf"/>.</param>
141+
/// <param name="path">The resulting path; null if the user didn't have an entry.</param>
142+
/// <returns>true if the call was successful (path may still be null); false is a larger buffer is needed.</returns>
143+
private static unsafe bool TryGetHomeDirectoryFromPasswd(byte* buf, int bufLen, out string path)
144+
{
145+
while (true)
146+
{
147+
// Call getpwuid_r to get the passwd struct
148+
Interop.Sys.Passwd passwd;
149+
IntPtr result;
150+
int rv = Interop.Sys.GetPwUid(Interop.Sys.GetEUid(), out passwd, buf, bufLen, out result);
151+
152+
// If the call succeeds, give back the home directory path retrieved
153+
if (rv == 0)
154+
{
155+
if (result == IntPtr.Zero)
156+
{
157+
// Current user's entry could not be found
158+
path = null; // we'll still return true, as false indicates the buffer was too small
159+
}
160+
else
161+
{
162+
Debug.Assert(result == (IntPtr)(&passwd));
163+
Debug.Assert(passwd.HomeDirectory != null);
164+
path = Marshal.PtrToStringAnsi((IntPtr)passwd.HomeDirectory);
165+
}
166+
return true;
167+
}
168+
169+
// If the call failed because it was interrupted, try again.
170+
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
171+
if (errorInfo.Error == Interop.Error.EINTR)
172+
continue;
173+
174+
// If the call failed because the buffer was too small, return false to
175+
// indicate the caller should try again with a larger buffer.
176+
if (errorInfo.Error == Interop.Error.ERANGE)
177+
{
178+
path = null;
179+
return false;
180+
}
181+
182+
// Otherwise, fail.
183+
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
184+
}
185+
}
186+
97187
}
98188
}

src/Native/System.Native/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ set(NATIVE_SOURCES
44
pal_errno.cpp
55
pal_exec.cpp
66
pal_stat.cpp
7+
pal_uid.cpp
78
)
89

910
add_library(System.Native

src/Native/System.Native/pal_uid.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
#include "../config.h"
5+
#include "pal_uid.h"
6+
7+
#include <assert.h>
8+
#include <stdlib.h>
9+
#include <unistd.h>
10+
#include <sys/types.h>
11+
#include <pwd.h>
12+
13+
/**
14+
* Gets a password structure for the given uid.
15+
* Implemented as shim to getpwuid_r(3).
16+
*
17+
* Returns 0 for success, -1 for failure. Sets errno on failure.
18+
*/
19+
extern "C"
20+
int32_t GetPwUid(
21+
int32_t uid,
22+
Passwd* pwd,
23+
char* buf,
24+
int64_t buflen,
25+
Passwd** result)
26+
{
27+
assert(pwd != nullptr);
28+
assert(buf != nullptr);
29+
assert(buflen >= 0);
30+
assert(result != nullptr);
31+
32+
struct passwd nativePwd;
33+
struct passwd* nativePwdResultPtr;
34+
int rv = getpwuid_r(uid, &nativePwd, buf, buflen, &nativePwdResultPtr);
35+
36+
// If successful, the result will be null if the user couldn't be found,
37+
// or it'll contain the address of the provided pwd structure, into
38+
// which the results are stored.
39+
if (rv == 0 && nativePwdResultPtr != nullptr)
40+
{
41+
assert(nativePwdResultPtr == &nativePwd);
42+
43+
pwd->Name = nativePwd.pw_name;
44+
pwd->Password = nativePwd.pw_passwd;
45+
pwd->UserId = nativePwd.pw_uid;
46+
pwd->GroupId = nativePwd.pw_gid;
47+
pwd->UserInfo = nativePwd.pw_gecos;
48+
pwd->HomeDirectory = nativePwd.pw_dir;
49+
pwd->Shell = nativePwd.pw_shell;
50+
*result = pwd;
51+
}
52+
else
53+
{
54+
// Make sure we zero out the results fields, as the managed
55+
// caller could be using out variables and expect them to be initialized
56+
// after the call.
57+
*pwd = { };
58+
*result = nullptr;
59+
}
60+
61+
return rv;
62+
}
63+
64+
extern "C"
65+
int32_t GetEUid()
66+
{
67+
return geteuid();
68+
}
69+
70+
extern "C"
71+
int32_t GetEGid()
72+
{
73+
return getegid();
74+
}

src/Native/System.Native/pal_uid.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
#pragma once
5+
6+
#include <stdint.h>
7+
8+
/**
9+
* Passwd struct
10+
*/
11+
struct Passwd {
12+
char* Name;
13+
char* Password;
14+
int32_t UserId;
15+
int32_t GroupId;
16+
char* UserInfo;
17+
char* HomeDirectory;
18+
char* Shell;
19+
};
20+
21+
/**
22+
* Gets a password structure for the given uid.
23+
* Implemented as shim to getpwuid_r(3).
24+
*
25+
* Returns 0 for success, -1 for failure. Sets errno on failure.
26+
*/
27+
extern "C"
28+
int32_t GetPwUid(
29+
int32_t uid,
30+
Passwd* pwd,
31+
char* buf,
32+
int64_t buflen,
33+
Passwd** result);
34+
35+
/**
36+
* Gets and returns the effective user's identity.
37+
* Implemented as shim to geteuid(2).
38+
*
39+
* Always succeeds.
40+
*/
41+
extern "C"
42+
int32_t GetEUid();
43+
44+
/**
45+
* Gets and returns the effective group's identity.
46+
* Implemented as shim to getegid(2).
47+
*
48+
* Always succeeds.
49+
*/
50+
extern "C"
51+
int32_t GetEGid();

src/System.Console/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,7 @@
198198
<data name="PlatformNotSupported_GettingColor" xml:space="preserve">
199199
<value>This platform does not support getting the current color.</value>
200200
</data>
201+
<data name="PersistedFiles_NoHomeDirectory" xml:space="preserve">
202+
<value>The home directory of the current user could not be determined.</value>
203+
</data>
201204
</root>

src/System.Console/src/System.Console.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@
9696
<Compile Include="$(CommonPath)\Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs">
9797
<Link>Common\Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs</Link>
9898
</Compile>
99+
<Compile Include="$(CommonPath)\System\IO\PersistedFiles.Unix.cs">
100+
<Link>Common\System\IO\PersistedFiles.Unix.cs</Link>
101+
</Compile>
102+
<Compile Include="$(CommonPath)\System\IO\PersistedFiles.Names.Unix.cs">
103+
<Link>Common\System\IO\PersistedFiles.Names.Unix.cs</Link>
104+
</Compile>
99105
<Compile Include="$(CommonPath)\System\IO\StringBuilderCache.cs">
100106
<Link>Common\System\IO\StringBuilderCache.cs</Link>
101107
</Compile>
@@ -132,6 +138,12 @@
132138
<Compile Include="$(CommonPath)\Interop\Unix\libc\Interop.snprintf.cs">
133139
<Link>Common\Interop\Unix\Interop.snprintf.cs</Link>
134140
</Compile>
141+
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetEUid.cs">
142+
<Link>Common\Interop\Unix\Interop.GetEUid.cs</Link>
143+
</Compile>
144+
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetPwUid.cs">
145+
<Link>Common\Interop\Unix\Interop.GetPwUid.cs</Link>
146+
</Compile>
135147
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Stat.cs">
136148
<Link>Common\Interop\Unix\Interop.Stat.cs</Link>
137149
</Compile>

src/System.Console/src/System/ConsolePal.Unix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ private static Database ReadDatabase(string term)
490490
}
491491

492492
// Then try in the user's home directory.
493-
string home = Environment.GetEnvironmentVariable("HOME");
493+
string home = PersistedFiles.GetHomeDirectory();
494494
if (!string.IsNullOrWhiteSpace(home) && (db = ReadDatabase(term, home + "/.terminfo")) != null)
495495
{
496496
return db;

0 commit comments

Comments
 (0)