1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2008 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
17 
18 #include "cpp.h"
19 
20 #ifdef _WIN32
21 #include "boinc_win.h"
22 #else
23 #include "config.h"
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <grp.h>
30 #endif
31 
32 #ifdef _MSC_VER
33 #define snprintf _snprintf
34 #endif
35 
36 #include "error_numbers.h"
37 #include "file_names.h"
38 #include "util.h"
39 #include "str_util.h"
40 #include "str_replace.h"
41 #include "filesys.h"
42 #include "parse.h"
43 #include "client_msgs.h"
44 #include "client_state.h"
45 
46 #include "sandbox.h"
47 
48 bool g_use_sandbox = false;
49 
50 #ifndef _WIN32
51 
52 // POSIX requires that shells run from an application will use the
53 // real UID and GID if different from the effective UID and GID.
54 // Mac OS 10.4 did not enforce this, but OS 10.5 does.  Since
55 // system() invokes a shell, we can't use it to run the switcher
56 // or setprojectgrp utilities, so we must do a fork() and execv().
57 //
switcher_exec(const char * util_filename,const char * cmdline)58 int switcher_exec(const char *util_filename, const char* cmdline) {
59     char* argv[100];
60     char util_path[MAXPATHLEN];
61     char command [1024];
62     char buffer[1024];
63     int fds_out[2], fds_err[2];
64     int stat;
65     int retval;
66     string output_out, output_err;
67 
68     snprintf(util_path, sizeof(util_path), "%s/%s", SWITCHER_DIR, util_filename);
69     argv[0] = const_cast<char*>(util_filename);
70     // Make a copy of cmdline because parse_command_line modifies it
71     safe_strcpy(command, cmdline);
72     parse_command_line(const_cast<char*>(cmdline), argv+1);
73 
74     // Create the output pipes
75     if (pipe(fds_out) == -1) {
76         perror("pipe() for fds_out failed in switcher_exec");
77         return ERR_PIPE;
78     }
79 
80     if (pipe(fds_err) == -1) {
81         perror("pipe() for fds_err failed in switcher_exec");
82         return ERR_PIPE;
83     }
84 
85     int pid = fork();
86     if (pid == -1) {
87         perror("fork() failed in switcher_exec");
88         return ERR_FORK;
89     }
90     if (pid == 0) {
91         // This is the new (forked) process
92 
93         // Setup pipe redirects
94         while ((dup2(fds_out[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
95         while ((dup2(fds_err[1], STDERR_FILENO) == -1) && (errno == EINTR)) {}
96         // Child only needs one-way (write) pipes so close read pipes
97         close(fds_out[0]);
98         close(fds_err[0]);
99 
100         execv(util_path, argv);
101         fprintf(stderr, "execv failed in switcher_exec(%s, %s): %s", util_path, cmdline, strerror(errno));
102 
103         _exit(EXIT_FAILURE);
104     }
105     // Parent only needs one-way (read) pipes so close write pipes
106     close(fds_out[1]);
107     close(fds_err[1]);
108 
109     // Capture stdout output
110     while (1) {
111         ssize_t count = read(fds_out[0], buffer, sizeof(buffer));
112         if (count == -1) {
113             if (errno == EINTR) {
114                 continue;
115             } else {
116                 break;
117             }
118         } else if (count == 0) {
119             break;
120         } else {
121             buffer[count] = '\0';
122             output_out += buffer;
123         }
124     }
125 
126     // Capture stderr output
127     while (1) {
128         ssize_t count = read(fds_err[0], buffer, sizeof(buffer));
129         if (count == -1) {
130             if (errno == EINTR) {
131                 continue;
132             } else {
133                 break;
134             }
135         } else if (count == 0) {
136             break;
137         } else {
138             buffer[count] = '\0';
139             output_err += buffer;
140         }
141     }
142 
143     // Wait for command to complete, like system() does.
144     waitpid(pid, &stat, 0);
145 
146     // Close pipe descriptors
147     close(fds_out[0]);
148     close(fds_err[0]);
149 
150     if (WIFEXITED(stat)) {
151         retval = WEXITSTATUS(stat);
152 
153         if (retval) {
154             if (log_flags.task_debug) {
155                 msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug] failure in switcher_exec");
156                 msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug]   switcher: %s", util_path);
157                 msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug]    command: %s", command);
158                 msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug]  exit code: %d", retval);
159                 msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug]     stdout: %s", output_out.c_str());
160                 msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug]     stderr: %s", output_err.c_str());
161             }
162         }
163         return retval;
164     }
165 
166     return 0;
167 }
168 
kill_via_switcher(int pid)169 int kill_via_switcher(int pid) {
170     char cmd[1024];
171 
172     if (!g_use_sandbox) return 0;
173 
174     // if project application is running as user boinc_project and
175     // client is running as user boinc_master,
176     // we cannot send a signal directly, so use switcher.
177     //
178     snprintf(cmd, sizeof(cmd), "/bin/kill kill -s KILL %d", pid);
179     return switcher_exec(SWITCHER_FILE_NAME, cmd);
180 }
181 
182 #ifndef _DEBUG
lookup_group(const char * name,gid_t & gid)183 static int lookup_group(const char* name, gid_t& gid) {
184     struct group* gp = getgrnam(name);
185     if (!gp) return ERR_GETGRNAM;
186     gid = gp->gr_gid;
187     return 0;
188 }
189 #endif
190 
remove_project_owned_file_or_dir(const char * path)191 int remove_project_owned_file_or_dir(const char* path) {
192     char cmd[1024];
193 
194     if (g_use_sandbox) {
195         snprintf(cmd, sizeof(cmd), "/bin/rm rm -fR \"%s\"", path);
196         if (switcher_exec(SWITCHER_FILE_NAME, cmd)) {
197             return ERR_UNLINK;
198         } else {
199             return 0;
200         }
201     }
202     return ERR_UNLINK;
203 }
204 
get_project_gid()205 int get_project_gid() {
206     if (g_use_sandbox) {
207 #ifdef _DEBUG
208         // GDB can't attach to applications which are running as a different user
209         //  or group, so fix up data with current user and group during debugging
210         gstate.boinc_project_gid = getegid();
211 #else
212         return lookup_group(BOINC_PROJECT_GROUP_NAME, gstate.boinc_project_gid);
213 #endif  // _DEBUG
214     } else {
215         gstate.boinc_project_gid = 0;
216     }
217     return 0;
218 }
219 
set_to_project_group(const char * path)220 int set_to_project_group(const char* path) {
221     if (g_use_sandbox) {
222         if (switcher_exec(SETPROJECTGRP_FILE_NAME, path)) {
223             return ERR_CHOWN;
224         }
225     }
226     return 0;
227 }
228 
229 #else
get_project_gid()230 int get_project_gid() {
231     return 0;
232 }
set_to_project_group(const char *)233 int set_to_project_group(const char*) {
234     return 0;
235 }
236 #endif // ! _WIN32
237 
238 // delete a file.
239 // return success if we deleted it or it didn't exist in the first place
240 //
delete_project_owned_file_aux(const char * path)241 static int delete_project_owned_file_aux(const char* path) {
242 #ifdef _WIN32
243     if (DeleteFile(path)) return 0;
244     int error = GetLastError();
245     if (error == ERROR_FILE_NOT_FOUND) {
246         return 0;
247     }
248     if (error == ERROR_ACCESS_DENIED) {
249         SetFileAttributes(path, FILE_ATTRIBUTE_NORMAL);
250         if (DeleteFile(path)) return 0;
251     }
252     return error;
253 #else
254     int retval = unlink(path);
255     if (retval == 0) return 0;
256     if (errno == ENOENT) {
257         return 0;
258     }
259     if (g_use_sandbox && (errno == EACCES)) {
260         // We may not have permission to read subdirectories created by projects
261         //
262         return remove_project_owned_file_or_dir(path);
263     }
264     return ERR_UNLINK;
265 #endif
266 }
267 
268 // Delete the file located at path.
269 // If "retry" is set, do retries for 5 sec in case some
270 // other program (e.g. virus checker) has the file locked.
271 // Don't do this if deleting directories - it can lock up the Manager.
272 //
delete_project_owned_file(const char * path,bool retry)273 int delete_project_owned_file(const char* path, bool retry) {
274     int retval = 0;
275 
276     retval = delete_project_owned_file_aux(path);
277     if (retval && retry) {
278         if (log_flags.slot_debug) {
279             msg_printf(0, MSG_INFO,
280                 "[slot] delete of %s failed (%d); retrying", path, retval
281             );
282         }
283         double start = dtime();
284         do {
285             boinc_sleep(drand()*2);       // avoid lockstep
286             retval = delete_project_owned_file_aux(path);
287             if (!retval) break;
288         } while (dtime() < start + FILE_RETRY_INTERVAL);
289     }
290     if (retval) {
291         if (log_flags.slot_debug) {
292             msg_printf(0, MSG_INFO,
293                 "[slot] failed to remove file %s: %s",
294                 path, boincerror(retval)
295             );
296         }
297         safe_strcpy(boinc_failed_file, path);
298         return ERR_UNLINK;
299     }
300     if (log_flags.slot_debug) {
301         msg_printf(0, MSG_INFO, "[slot] removed file %s", path);
302     }
303     return 0;
304 }
305 
306 // recursively delete everything in the specified directory
307 // (but not the directory itself).
308 // If an error occurs, delete as much as possible.
309 //
client_clean_out_dir(const char * dirpath,const char * reason,const char * except)310 int client_clean_out_dir(
311     const char* dirpath, const char* reason, const char* except
312 ) {
313     char filename[MAXPATHLEN], path[MAXPATHLEN];
314     int retval, final_retval = 0;
315     DIRREF dirp;
316 
317     if (reason && log_flags.slot_debug) {
318         msg_printf(0, MSG_INFO, "[slot] cleaning out %s: %s", dirpath, reason);
319     }
320     dirp = dir_open(dirpath);
321     if (!dirp) {
322 #ifndef _WIN32
323         if (g_use_sandbox && (errno == EACCES)) {
324             // dir may be owned by boinc_apps
325             return remove_project_owned_file_or_dir(dirpath);
326         }
327 #endif
328         return 0;    // if dir doesn't exist, it's empty
329     }
330 
331     while (1) {
332         safe_strcpy(filename, "");
333         retval = dir_scan(filename, dirp, sizeof(filename));
334         if (retval) {
335             if (retval != ERR_NOT_FOUND) {
336                 if (log_flags.slot_debug) {
337                     msg_printf(0, MSG_INFO,
338                         "[slot] dir_scan(%s) failed: %s",
339                         dirpath, boincerror(retval)
340                     );
341                 }
342                 final_retval = retval;
343             }
344             break;
345         }
346         if (except && !strcmp(except, filename)) {
347             continue;
348         }
349         snprintf(path, sizeof(path), "%s/%s", dirpath,  filename);
350         if (is_dir(path)) {
351             retval = client_clean_out_dir(path, NULL);
352             if (retval) final_retval = retval;
353             retval = remove_project_owned_dir(path);
354             if (retval) final_retval = retval;
355         } else {
356             retval = delete_project_owned_file(path, false);
357             if (retval) final_retval = retval;
358         }
359     }
360     dir_close(dirp);
361     return final_retval;
362 }
363 
remove_project_owned_dir(const char * name)364 int remove_project_owned_dir(const char* name) {
365 #ifdef _WIN32
366     if (!RemoveDirectory(name)) {
367         return GetLastError();
368     }
369     return 0;
370 #else
371     int retval;
372     retval = rmdir(name);
373     // We may not have permission to read subdirectories created by projects
374     if (retval && g_use_sandbox && (errno == EACCES)) {
375         retval = remove_project_owned_file_or_dir(name);
376     }
377     return retval;
378 #endif
379 }
380