master
cpp 262 lines 6.02 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2 #include "precomp.h"
3 #include "p9util.h"
4 #include <pwd.h>
5 #include <grp.h>
6 #include <syscall.h>
7
8 #define _LINUX_CAPABILITY_VERSION_3 0x20080522
9 #define CAP_FOWNER 3
10 #define CAP_TO_INDEX(Cap) ((Cap) >> 5)
11 #define CAP_TO_MASK(Cap) (1 << ((Cap) & 31))
12
13 struct cap_user_header_t
14 {
15 std::uint32_t version;
16 pid_t pid;
17 };
18
19 struct cap_user_data_t
20 {
21 std::uint32_t effective;
22 std::uint32_t permitted;
23 std::uint32_t inheritable;
24 };
25
26 inline int sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
27 {
28 return syscall(SYS_setresuid, ruid, euid, suid);
29 }
30
31 inline int sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
32 {
33 return syscall(SYS_setresgid, rgid, egid, sgid);
34 }
35
36 inline int sys_faccessat(int dirFd, const char* pathName, int mode, int flags)
37 {
38 return syscall(SYS_faccessat, dirFd, pathName, mode, flags);
39 }
40
41 inline int sys_setgroups(size_t size, const gid_t* list)
42 {
43 return syscall(SYS_setgroups, size, list);
44 }
45
46 constexpr long c_PasswordFileBufferSize = 1024;
47
48 namespace p9fs::util {
49
50 Expected<wil::unique_fd> OpenAt(int dirfd, const std::string& name, int openFlags, mode_t mode)
51 {
52 if (name.length() == 0)
53 {
54 return Reopen(dirfd, openFlags);
55 }
56
57 int fd = openat(dirfd, name.c_str(), openFlags | O_CLOEXEC, mode);
58 if (fd < 0)
59 {
60 return LxError{-errno};
61 }
62
63 return wil::unique_fd{fd};
64 }
65
66 Expected<wil::unique_fd> Reopen(int fd, int openFlags)
67 {
68 const char* pathToOpen;
69 std::string targetPath;
70 char fdPath[PATH_MAX];
71
72 // If O_NOFOLLOW is set, open the target of the link in proc directly,
73 // otherwise the call will always fail.
74 if (WI_IsFlagSet(openFlags, O_NOFOLLOW))
75 {
76 targetPath = GetFdPath(fd);
77 pathToOpen = targetPath.c_str();
78 }
79 else
80 {
81 snprintf(fdPath, sizeof(fdPath), "/proc/self/fd/%d", fd);
82 pathToOpen = fdPath;
83 }
84
85 int newFd = open(pathToOpen, openFlags | O_CLOEXEC);
86 if (newFd < 0)
87 {
88 return LxError{-errno};
89 }
90
91 return wil::unique_fd{newFd};
92 }
93
94 std::string GetFdPath(int fd)
95 {
96 char fdPath[PATH_MAX]{};
97 snprintf(fdPath, sizeof(fdPath), "/proc/self/fd/%d", fd);
98 char target[PATH_MAX]{};
99 const int result = readlink(fdPath, target, sizeof(target));
100 THROW_LAST_ERROR_IF(result < 0);
101 return {target, static_cast<size_t>(result)};
102 }
103
104 LX_INT LinuxErrorFromCaughtException()
105 {
106 return -wil::ResultFromCaughtException();
107 }
108
109 LX_INT AccessHelper(int fd, const std::string& path, int mode)
110 {
111 const char* pathToCheck = path.c_str();
112 std::string fdPath;
113 if (path.length() == 0)
114 {
115 // AT_EMPTY_PATH is not supported by faccessat, so get the full path
116 // to the target if an access check is to be performed on the specified
117 // directory.
118 fdPath = GetFdPath(fd);
119 pathToCheck = fdPath.c_str();
120 }
121
122 // The musl wrapper incorrectly blocks AT_SYMLINK_NOFOLLOW, so call the syscall directly.
123 int result = sys_faccessat(fd, pathToCheck, mode, AT_SYMLINK_NOFOLLOW | AT_EACCESS);
124 if (result < 0)
125 {
126 return -errno;
127 }
128
129 return {};
130 }
131
132 LX_INT CheckFOwnerCapability()
133 {
134 cap_user_header_t header{};
135 cap_user_data_t data[2]{};
136 header.version = _LINUX_CAPABILITY_VERSION_3;
137 int result = syscall(SYS_capget, &header, data);
138 if (result < 0)
139 {
140 return -errno;
141 }
142
143 if (WI_IsFlagSet(data[CAP_TO_INDEX(CAP_FOWNER)].effective, CAP_TO_MASK(CAP_FOWNER)))
144 {
145 return {};
146 }
147
148 return LX_EPERM;
149 }
150
151 gid_t GetUserGroupId(uid_t uid)
152 {
153 long size = sysconf(_SC_GETPW_R_SIZE_MAX);
154 if (size < 0)
155 {
156 size = c_PasswordFileBufferSize;
157 }
158
159 std::vector<char> buffer;
160 struct passwd pwd;
161 struct passwd* result;
162 for (;;)
163 {
164 buffer.resize(size);
165 if (getpwuid_r(uid, &pwd, buffer.data(), size, &result) != 0)
166 {
167 if (errno != ERANGE)
168 {
169 return c_InvalidGid;
170 }
171
172 size += c_PasswordFileBufferSize;
173 continue;
174 }
175
176 break;
177 }
178
179 if (result == nullptr)
180 {
181 return c_InvalidGid;
182 }
183
184 return result->pw_gid;
185 }
186
187 gid_t GetGroupIdByName(const char* name)
188 {
189 long size = sysconf(_SC_GETGR_R_SIZE_MAX);
190 if (size < 0)
191 {
192 size = c_PasswordFileBufferSize;
193 }
194
195 std::vector<char> buffer;
196 struct group grp;
197 struct group* result;
198 for (;;)
199 {
200 buffer.resize(size);
201 if (getgrnam_r(name, &grp, buffer.data(), size, &result) != 0)
202 {
203 if (errno != ERANGE)
204 {
205 return c_InvalidGid;
206 }
207
208 size += c_PasswordFileBufferSize;
209 continue;
210 }
211
212 break;
213 }
214
215 if (result == nullptr)
216 {
217 return c_InvalidGid;
218 }
219
220 return result->gr_gid;
221 }
222
223 // Sets the effective uid and gid of the thread to the specified values.
224 FsUserContext::FsUserContext(uid_t uid, gid_t gid, const std::vector<gid_t>& groups)
225 {
226 if (!groups.empty())
227 {
228 THROW_LAST_ERROR_IF(sys_setgroups(groups.size(), groups.data()) < 0);
229 m_restoreGroups = true;
230 }
231
232 if (uid != c_InvalidUid)
233 {
234 m_Restore = true;
235 // Use the syscall directly since the wrappers change the value on all threads.
236 // Set the GID first since the capability to do that is lost once the UID changes to non-root.
237 THROW_LAST_ERROR_IF(sys_setresgid(c_InvalidGid, gid, c_InvalidGid) < 0);
238 THROW_LAST_ERROR_IF(sys_setresuid(c_InvalidUid, uid, c_InvalidUid) < 0);
239 }
240 }
241
242 // Restores the effective uid and gid to root.
243 FsUserContext::~FsUserContext()
244 {
245 try
246 {
247 if (m_Restore)
248 {
249 // Use the syscall directly since the wrappers change the value on all threads.
250 THROW_LAST_ERROR_IF(sys_setresuid(-1, 0, -1) < 0);
251 THROW_LAST_ERROR_IF(sys_setresgid(c_InvalidGid, 0, c_InvalidGid) < 0);
252 }
253
254 if (m_restoreGroups)
255 {
256 THROW_LAST_ERROR_IF(sys_setgroups(0, nullptr) < 0);
257 }
258 }
259 CATCH_LOG()
260 }
261
262 } // namespace p9fs::util