1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "execns"
18 #include <log/log.h>
19
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <grp.h>
23 #include <pwd.h>
24 #include <sched.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30
31 #include <string>
32 #include <vector>
33
34 static bool isTerminal = false;
35 // Print errors to stderr if running from a terminal, otherwise print to logcat
36 // This is useful for debugging from a terminal
37 #define LOGE(...) do { \
38 if (isTerminal) { \
39 fprintf(stderr, __VA_ARGS__); \
40 fprintf(stderr, "\n"); \
41 } else { \
42 ALOGE(__VA_ARGS__); \
43 } \
44 } while (0)
45
46 static const char kNetNsDir[] = "/data/vendor/var/run/netns";
47
48 class FileDescriptor {
49 public:
FileDescriptor(int fd)50 explicit FileDescriptor(int fd) : mFd(fd) { }
51 FileDescriptor(const FileDescriptor&) = delete;
~FileDescriptor()52 ~FileDescriptor() {
53 if (mFd != -1) {
54 close(mFd);
55 mFd = -1;
56 }
57 }
get() const58 int get() const { return mFd; }
59 FileDescriptor& operator=(const FileDescriptor&) = delete;
60 private:
61 int mFd;
62 };
63
64 class File {
65 public:
File(FILE * file)66 explicit File(FILE* file) : mFile(file) { }
67 File(const File&) = delete;
~File()68 ~File() {
69 if (mFile) {
70 ::fclose(mFile);
71 mFile = nullptr;
72 }
73 }
74
get() const75 FILE* get() const { return mFile; }
76 File& operator=(const File&) = delete;
77 private:
78 FILE* mFile;
79 };
80
printUsage(const char * program)81 static void printUsage(const char* program) {
82 LOGE("%s [-u user] [-g group] <namespace> <program> [options...]", program);
83 }
84
isNumericString(const char * str)85 static bool isNumericString(const char* str) {
86 while (isdigit(*str)) {
87 ++str;
88 }
89 return *str == '\0';
90 }
91
readNamespacePid(const char * ns)92 static std::string readNamespacePid(const char* ns) {
93 char nsPath[PATH_MAX];
94 snprintf(nsPath, sizeof(nsPath), "%s/%s.pid", kNetNsDir, ns);
95
96 File file(::fopen(nsPath, "r"));
97 if (file.get() == nullptr) {
98 LOGE("Unable to open file %s for namespace %s: %s",
99 nsPath, ns, strerror(errno));
100 return std::string();
101 }
102
103 char buffer[32];
104 size_t bytesRead = ::fread(buffer, 1, sizeof(buffer), file.get());
105 if (bytesRead < sizeof(buffer) && feof(file.get())) {
106 // Reached end-of-file, null-terminate
107 buffer[bytesRead] = '\0';
108 if (isNumericString(buffer)) {
109 // File is valid and contains a number, return it
110 return buffer;
111 }
112 LOGE("File %s does not contain a valid pid '%s'", nsPath, buffer);
113 } else if (ferror(file.get())) {
114 LOGE("Error reading from file %s: %s", nsPath, strerror(errno));
115 } else {
116 LOGE("Invalid contents of pid file %s", nsPath);
117 }
118 return std::string();
119 }
120
setNetworkNamespace(const char * ns)121 static bool setNetworkNamespace(const char* ns) {
122 // There is a file in the net namespace dir (/data/vendor/var/run/netns) with
123 // the name "<namespace>.pid". This file contains the pid of the createns
124 // process that created the namespace.
125 //
126 // To switch network namespace we're going to call setns which requires an
127 // open file descriptor to /proc/<pid>/ns/net where <pid> refers to a
128 // process already running in that namespace. So using the pid from the file
129 // above we can determine which path to use.
130 std::string pid = readNamespacePid(ns);
131 if (pid.empty()) {
132 return false;
133 }
134 char nsPath[PATH_MAX];
135 snprintf(nsPath, sizeof(nsPath), "/proc/%s/ns/net", pid.c_str());
136
137 FileDescriptor nsFd(open(nsPath, O_RDONLY | O_CLOEXEC));
138 if (nsFd.get() == -1) {
139 LOGE("Cannot open network namespace '%s' at '%s': %s",
140 ns, nsPath, strerror(errno));
141 return false;
142 }
143
144 if (setns(nsFd.get(), CLONE_NEWNET) == -1) {
145 LOGE("Cannot set network namespace '%s': %s",
146 ns, strerror(errno));
147 return false;
148 }
149 return true;
150 }
151
changeUser(const char * user)152 static bool changeUser(const char* user) {
153 struct passwd* pwd = ::getpwnam(user);
154 if (pwd == nullptr) {
155 LOGE("Could not find user '%s'", user);
156 return false;
157 }
158
159 if (::setuid(pwd->pw_uid) != 0) {
160 LOGE("Cannot switch to user '%s': %s", user, strerror(errno));
161 return false;
162 }
163 return true;
164 }
165
changeGroup(const char * group)166 static bool changeGroup(const char* group) {
167 struct group* grp = ::getgrnam(group);
168 if (grp == nullptr) {
169 LOGE("Could not find group '%s'", group);
170 return false;
171 }
172
173 if (::setgid(grp->gr_gid) != 0) {
174 LOGE("Cannot switch to group '%s': %s", group, strerror(errno));
175 return false;
176 }
177 return true;
178 }
179
180 // Append a formatted string to the end of |buffer|. The total size in |buffer|
181 // is |size|, including any existing string data. The string to append is
182 // specified by |fmt| and any additional arguments required by the format
183 // string. If the function fails it returns -1, otherwise it returns the number
184 // of characters printed (excluding the terminating NULL). On success the
185 // string is always null-terminated.
sncatf(char * buffer,size_t size,const char * fmt,...)186 static int sncatf(char* buffer, size_t size, const char* fmt, ...) {
187 size_t len = strnlen(buffer, size);
188 if (len >= size) {
189 // The length exceeds the available size, if len == size then there is
190 // also a terminating null after len bytes which would then be outside
191 // the provided buffer.
192 return -1;
193 }
194
195 va_list args;
196 va_start(args, fmt);
197 int printed = vsnprintf(buffer + len, size - len, fmt, args);
198 buffer[size - 1] = '\0';
199 va_end(args);
200 return printed;
201 }
202
203 /**
204 * Execute a given |command| with |argc| number of parameters that are located
205 * in |argv|. The first parameter in |argv| is the command that should be run
206 * followed by its arguments.
207 */
execCommand(int argc,char ** argv)208 static int execCommand( int argc, char** argv) {
209 if (argc <= 0 || argv == nullptr || argv[0] == nullptr) {
210 LOGE("No command specified");
211 return 1;
212 }
213
214 std::vector<char*> arguments;
215 // Place all the arguments in the vector and the terminating null
216 arguments.insert(arguments.begin(), argv, argv + argc);
217 arguments.push_back(nullptr);
218
219 char buffer[4096];
220 if (execvp(argv[0], arguments.data()) == -1) {
221 // Save errno in case it gets changed by printing stuff.
222 int error = errno;
223 int printed = snprintf(buffer, sizeof(buffer),
224 "Could not execute command '%s", argv[0]);
225 if (printed < 0) {
226 LOGE("Could not execute command: %s", strerror(error));
227 return error;
228 }
229 for (int i = 1; i < argc; ++i) {
230 // Be nice to the user and print quotes if there are spaces to
231 // indicate how we saw it. If there are already single quotes in
232 // there confusion will ensue.
233 if (strchr(argv[i], ' ')) {
234 sncatf(buffer, sizeof(buffer), " \"%s\"", argv[i]);
235 } else {
236 sncatf(buffer, sizeof(buffer), " %s", argv[i]);
237 }
238 }
239 sncatf(buffer, sizeof(buffer), "': %s", strerror(error));
240 LOGE("%s", buffer);
241 return error;
242 }
243 // execvp never returns unless it fails so this is just to return something.
244 return 0;
245 }
246
247 /**
248 * Enter a given network namespace argv[1] and execute command argv[2] with
249 * options argv[3..argc-1] in that namespace.
250 */
main(int argc,char * argv[])251 int main(int argc, char* argv[]) {
252 isTerminal = isatty(STDOUT_FILENO) != 0;
253
254 // Parse parameters
255 const char* user = nullptr;
256 const char* group = nullptr;
257 int nsArg = -1;
258 int execArg = -1;
259 for (int i = 1; i < argc; ++i) {
260 if (::strcmp(argv[i], "-u") == 0) {
261 if (user || i + 1 >= argc) {
262 LOGE("Missing argument to option -u");
263 return 1;
264 }
265 user = argv[++i];
266 } else if (::strcmp(argv[i], "-g") == 0) {
267 if (group || i + 1 >= argc) {
268 LOGE("Missing argument to option -g");
269 return 1;
270 }
271 group = argv[++i];
272 } else {
273 // Break on the first non-option and treat it as the namespace name
274 nsArg = i;
275 if (i + 1 < argc) {
276 execArg = i + 1;
277 }
278 break;
279 }
280 }
281
282 if (nsArg < 0 || execArg < 0) {
283 // Missing namespace and/or exec arguments
284 printUsage(argv[0]);
285 return 1;
286 }
287
288 // First set the new network namespace for this process
289 if (!setNetworkNamespace(argv[nsArg])) {
290 return 1;
291 }
292
293 // Changing namespace is the privileged operation, so now we can drop
294 // privileges by changing user and/or group if the user requested it. Note
295 // that it's important to change group first because it must be done as a
296 // privileged user. Otherwise an attacker might be able to restore group
297 // privileges by using the group ID that is saved by setgid when running
298 // as a non-privileged user.
299 if (group && !changeGroup(group)) {
300 return 1;
301 }
302
303 if (user && !changeUser(user)) {
304 return 1;
305 }
306
307 // Now run the command with all the remaining parameters
308 return execCommand(argc - execArg, &argv[execArg]);
309 }
310
311