• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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