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