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 <sched.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28
29 #include <string>
30 #include <vector>
31
32 static bool isTerminal = false;
33 // Print errors to stderr if running from a terminal, otherwise print to logcat
34 // This is useful for debugging from a terminal
35 #define LOGE(...) do { \
36 if (isTerminal) { \
37 fprintf(stderr, __VA_ARGS__); \
38 fprintf(stderr, "\n"); \
39 } else { \
40 ALOGE(__VA_ARGS__); \
41 } \
42 } while (0)
43
44 static const char kNetNsDir[] = "/data/vendor/var/run/netns";
45
46 class FileDescriptor {
47 public:
FileDescriptor(int fd)48 explicit FileDescriptor(int fd) : mFd(fd) { }
49 FileDescriptor(const FileDescriptor&) = delete;
~FileDescriptor()50 ~FileDescriptor() {
51 if (mFd != -1) {
52 close(mFd);
53 mFd = -1;
54 }
55 }
get() const56 int get() const { return mFd; }
57 FileDescriptor& operator=(const FileDescriptor&) = delete;
58 private:
59 int mFd;
60 };
61
62 class File {
63 public:
File(FILE * file)64 explicit File(FILE* file) : mFile(file) { }
65 File(const File&) = delete;
~File()66 ~File() {
67 if (mFile) {
68 ::fclose(mFile);
69 mFile = nullptr;
70 }
71 }
72
get() const73 FILE* get() const { return mFile; }
74 File& operator=(const File&) = delete;
75 private:
76 FILE* mFile;
77 };
78
printUsage(const char * program)79 static void printUsage(const char* program) {
80 LOGE("%s <namespace> <program> [options...]", program);
81 }
82
isNumericString(const char * str)83 static bool isNumericString(const char* str) {
84 while (isdigit(*str)) {
85 ++str;
86 }
87 return *str == '\0';
88 }
89
readNamespacePid(const char * ns)90 static std::string readNamespacePid(const char* ns) {
91 char nsPath[PATH_MAX];
92 snprintf(nsPath, sizeof(nsPath), "%s/%s.pid", kNetNsDir, ns);
93
94 File file(::fopen(nsPath, "r"));
95 if (file.get() == nullptr) {
96 LOGE("Unable to open file %s for namespace %s: %s",
97 nsPath, ns, strerror(errno));
98 return std::string();
99 }
100
101 char buffer[32];
102 size_t bytesRead = ::fread(buffer, 1, sizeof(buffer), file.get());
103 if (bytesRead < sizeof(buffer) && feof(file.get())) {
104 // Reached end-of-file, null-terminate
105 buffer[bytesRead] = '\0';
106 if (isNumericString(buffer)) {
107 // File is valid and contains a number, return it
108 return buffer;
109 }
110 LOGE("File %s does not contain a valid pid '%s'", nsPath, buffer);
111 } else if (ferror(file.get())) {
112 LOGE("Error reading from file %s: %s", nsPath, strerror(errno));
113 } else {
114 LOGE("Invalid contents of pid file %s", nsPath);
115 }
116 return std::string();
117 }
118
setNetworkNamespace(const char * ns)119 static bool setNetworkNamespace(const char* ns) {
120 // There is a file in the net namespace dir (/data/vendor/var/run/netns) with
121 // the name "<namespace>.pid". This file contains the pid of the createns
122 // process that created the namespace.
123 //
124 // To switch network namespace we're going to call setns which requires an
125 // open file descriptor to /proc/<pid>/ns/net where <pid> refers to a
126 // process already running in that namespace. So using the pid from the file
127 // above we can determine which path to use.
128 std::string pid = readNamespacePid(ns);
129 if (pid.empty()) {
130 return false;
131 }
132 char nsPath[PATH_MAX];
133 snprintf(nsPath, sizeof(nsPath), "/proc/%s/ns/net", pid.c_str());
134
135 FileDescriptor nsFd(open(nsPath, O_RDONLY | O_CLOEXEC));
136 if (nsFd.get() == -1) {
137 LOGE("Cannot open network namespace '%s' at '%s': %s",
138 ns, nsPath, strerror(errno));
139 return false;
140 }
141
142 if (setns(nsFd.get(), CLONE_NEWNET) == -1) {
143 LOGE("Cannot set network namespace '%s': %s",
144 ns, strerror(errno));
145 return false;
146 }
147 return true;
148 }
149
150 // Append a formatted string to the end of |buffer|. The total size in |buffer|
151 // is |size|, including any existing string data. The string to append is
152 // specified by |fmt| and any additional arguments required by the format
153 // string. If the function fails it returns -1, otherwise it returns the number
154 // of characters printed (excluding the terminating NULL). On success the
155 // string is always null-terminated.
sncatf(char * buffer,size_t size,const char * fmt,...)156 static int sncatf(char* buffer, size_t size, const char* fmt, ...) {
157 size_t len = strnlen(buffer, size);
158 if (len >= size) {
159 // The length exceeds the available size, if len == size then there is
160 // also a terminating null after len bytes which would then be outside
161 // the provided buffer.
162 return -1;
163 }
164
165 va_list args;
166 va_start(args, fmt);
167 int printed = vsnprintf(buffer + len, size - len, fmt, args);
168 buffer[size - 1] = '\0';
169 va_end(args);
170 return printed;
171 }
172
173 /**
174 * Execute a given |command| with |argc| number of parameters that are located
175 * in |argv|. The first parameter in |argv| is the command that should be run
176 * followed by its arguments.
177 */
execCommand(int argc,char ** argv)178 static int execCommand( int argc, char** argv) {
179 if (argc <= 0 || argv == nullptr || argv[0] == nullptr) {
180 LOGE("No command specified");
181 return 1;
182 }
183
184 std::vector<char*> arguments;
185 // Place all the arguments in the vector and the terminating null
186 arguments.insert(arguments.begin(), argv, argv + argc);
187 arguments.push_back(nullptr);
188
189 char buffer[4096];
190 if (execvp(argv[0], arguments.data()) == -1) {
191 // Save errno in case it gets changed by printing stuff.
192 int error = errno;
193 int printed = snprintf(buffer, sizeof(buffer),
194 "Could not execute command '%s", argv[0]);
195 if (printed < 0) {
196 LOGE("Could not execute command: %s", strerror(error));
197 return error;
198 }
199 for (int i = 1; i < argc; ++i) {
200 // Be nice to the user and print quotes if there are spaces to
201 // indicate how we saw it. If there are already single quotes in
202 // there confusion will ensue.
203 if (strchr(argv[i], ' ')) {
204 sncatf(buffer, sizeof(buffer), " \"%s\"", argv[i]);
205 } else {
206 sncatf(buffer, sizeof(buffer), " %s", argv[i]);
207 }
208 }
209 sncatf(buffer, sizeof(buffer), "': %s", strerror(error));
210 LOGE("%s", buffer);
211 return error;
212 }
213 // execvp never returns unless it fails so this is just to return something.
214 return 0;
215 }
216
217 /**
218 * Enter a given network namespace argv[1] and execute command argv[2] with
219 * options argv[3..argc-1] in that namespace.
220 */
main(int argc,char * argv[])221 int main(int argc, char* argv[]) {
222 isTerminal = isatty(STDOUT_FILENO) != 0;
223 if (argc < 3) {
224 printUsage(argv[0]);
225 return 1;
226 }
227
228 // First set the new network namespace for this process
229 if (!setNetworkNamespace(argv[1])) {
230 return 1;
231 }
232
233 // Now run the command with all the remaining parameters
234 return execCommand(argc - 2, &argv[2]);
235 }
236
237