void download_to_file(geoipupdate_s * gu, const char *url, const char *fname, char *expected_file_md5) { FILE *f = fopen(fname, "wb"); exit_if(NULL == f, "Can't open %s\n", fname); say_if(gu->verbose, "url: %s\n", url); CURL *curl = gu->curl; expected_file_md5[0] = '\0'; curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, get_expected_file_md5); curl_easy_setopt(curl, CURLOPT_HEADERDATA, expected_file_md5); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)f); curl_easy_setopt(curl, CURLOPT_URL, url); common_req(curl, gu); CURLcode res = curl_easy_perform(curl); exit_unless(res == CURLE_OK, "curl_easy_perform() failed: %s\nConnect to %s\n", curl_easy_strerror(res), url); long status = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); exit_if( status < 200 || status >= 300, "Received an unexpected HTTP status code of %ld from %s\n", status, url); exit_if(-1 == fclose(f), "Error closing stream: %s", strerror(errno)); }
int main(int argc, char *argv[]) { const char *root, *cwd, *env, *exe; exit_if(argc < 5, "Usage: %s /path/to/root /work/directory /env/file /to/exec [args ...]", argv[0]); root = argv[1]; cwd = argv[2]; env = argv[3]; exe = argv[4]; load_env(env); pexit_if(chroot(root) == -1, "Chroot \"%s\" failed", root); pexit_if(chdir(cwd) == -1, "Chdir \"%s\" failed", cwd); /* XXX(vc): note that since execvp() is happening post-chroot, the * app's environment settings correctly affect the PATH search. * This is why execvpe() isn't being used, we manipulate the environment * manually then let it potentially affect execvp(). execvpe() simply * passes the environment to execve() _after_ performing the search, not * what we want here. */ pexit_if(execvp(exe, &argv[4]) == -1 && errno != ENOENT && errno != EACCES, "Exec of \"%s\" failed", exe); diag(exe); return EXIT_FAILURE; }
/* The environment file must exist, may be empty, and is expected to be of the format: * key=value\0key=value\0... */ static void load_env(const char *env) { struct stat st; char *map, *k, *v; typeof(st.st_size) i; map_file(env, PROT_READ|PROT_WRITE, MAP_PRIVATE, &st, (void **)&map); pexit_if(clearenv() != 0, "Unable to clear environment"); if(!st.st_size) return; map[st.st_size - 1] = '\0'; /* ensure the mapping is null-terminated */ for(i = 0; i < st.st_size;) { k = &map[i]; i += strlen(k) + 1; exit_if((v = strchr(k, '=')) == NULL, "Malformed environment entry: \"%s\"", k); /* a private writable map is used permitting s/=/\0/ */ *v = '\0'; v++; pexit_if(setenv(k, v, 1) == -1, "Unable to set env variable: \"%s\"=\"%s\"", k, v); } }
// 입력받은 cmdline 문자열을 파싱하여 각 명령을 수행한다. void execute_cmdline(char* cmdline) { int count = makelist(cmdline, ";", cmdgrps, MAX_CMD_GRP); for(int i = 0; i < count; ++i) { // exit 명령 처리 exit_if(cmdgrps[i]); // cd 명령 처리 if(chdir_if_cd(cmdgrps[i])) continue; int pid = fork(); int status = 0; switch(pid) { case -1: fatal("fork error"); break; case 0: // child process execute_cmdgrp(cmdgrps[i]); exit(0); break; default: // parent process waitpid(pid, &status, 0); fflush(stdout); } } }
int md5hex(const char *fname, char *hex_digest) { int bsize = 1024; unsigned char buffer[bsize], digest[16]; size_t len; MD5_CONTEXT context; FILE *fh = fopen(fname, "rb"); if (fh == NULL) { strcpy(hex_digest, ZERO_MD5); return 0; } struct stat st; exit_unless(stat(fname, &st) == 0 && S_ISREG(st.st_mode), "%s is not a file\n", fname); md5_init(&context); while ((len = fread(buffer, 1, bsize, fh)) > 0) { md5_write(&context, buffer, len); } md5_final(&context); memcpy(digest, context.buf, 16); exit_if(-1 == fclose(fh), "Error closing stream: %s", strerror(errno)); for (int i = 0; i < 16; i++) { snprintf(&hex_digest[2 * i], 3, "%02x", digest[i]); } return 1; }
void xasprintf(char **ptr, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int rc = vasprintf(ptr, fmt, ap); va_end(ap); exit_if(rc == -1, "Out of memory\n"); }
static void mount_at(const char *root, const mount_point *mnt) { char to[4096]; exit_if(snprintf(to, sizeof(to), "%s/%s", root, mnt->target) >= sizeof(to), "Path too long: \"%s\"", to); pexit_if(mount(mnt->source, to, mnt->type, mnt->flags, mnt->options) == -1, "Mounting \"%s\" on \"%s\" failed", mnt->source, to); }
static int openpidfd(int pid, char *which) { char path[PATH_MAX]; int fd; exit_if(snprintf(path, sizeof(path), "/proc/%i/%s", pid, which) == sizeof(path), "Path overflow"); pexit_if((fd = open(path, O_RDONLY|O_CLOEXEC)) == -1, "Unable to open \"%s\"", path); return fd; }
int main(int argc, char *argv[]) { /* We need to keep these env variables since systemd uses them for socket * activation */ static const char *keep_env[] = { "LISTEN_FDS", "LISTEN_PID", NULL }; const char *root, *cwd, *env, *uid_str, *gid_str, *exe; char **args; uid_t uid; gid_t *gids; size_t n_gids; exit_if(argc < 7, "Usage: %s /path/to/root /work/directory /env/file uid gid[,gid...] /to/exec [args ...]", argv[0]); root = argv[1]; cwd = argv[2]; env = argv[3]; uid_str = argv[4]; uid = atoi(uid_str); gid_str = argv[5]; args = &argv[6]; exe = args[0]; parse_gids(gid_str, &n_gids, &gids); load_env(env, keep_env); pexit_if(chroot(root) == -1, "Chroot \"%s\" failed", root); pexit_if(chdir(cwd) == -1, "Chdir \"%s\" failed", cwd); pexit_if(gids[0] > 0 && setresgid(gids[0], gids[0], gids[0]) == -1, "Setresgid \"%s\" failed", gid_str); pexit_if(n_gids > 1 && setgroups(n_gids - 1, &gids[1]) == -1, "Setgroups \"%s\" failed", gid_str); pexit_if(uid > 0 && setresuid(uid, uid, uid) == -1, "Setresuid \"%s\" failed", uid_str); /* XXX(vc): note that since execvp() is happening post-chroot, the * app's environment settings correctly affect the PATH search. * This is why execvpe() isn't being used, we manipulate the environment * manually then let it potentially affect execvp(). execvpe() simply * passes the environment to execve() _after_ performing the search, not * what we want here. */ pexit_if(execvp(exe, args) == -1 && errno != ENOENT && errno != EACCES, "Exec of \"%s\" failed", exe); diag(exe); return EXIT_FAILURE; }
/* Parse a comma-separated list of numeric gids from str, returns an malloc'd * array of gids in *gids_p with the number of elements in *n_gids_p. */ static void parse_gids(const char *str, size_t *n_gids_p, gid_t **gids_p) { char c = ',', last_c; int i, n_gids = 0, done = 0; gid_t gid = 0; gid_t *gids = NULL; for(i = 0; !done; i++) { last_c = c; switch(c = str[i]) { case '0' ... '9': gid *= 10; gid += c - '0'; break; case '\0': done = 1; /* fallthrough */ case ',': exit_if(last_c == ',', "Gids contains an empty gid: \"%s\"", str); pexit_if((gids = realloc(gids, sizeof(*gids) * (n_gids + 1))) == NULL, "Unable to allocate gids: \"%s\"", str); gids[n_gids++] = gid; gid = 0; break; default: exit_if(1, "Gids contains invalid input (%c): \"%s\"", c, str); } } exit_if(!n_gids, "At least one gid is required, got: \"%s\"", str); *gids_p = gids; *n_gids_p = n_gids; }
static void map_file(const char *path, int prot, int flags, struct stat *st, void **map) { int fd; pexit_if((fd = open(path, O_RDONLY)) == -1, "Unable to open \"%s\"", path); pexit_if(fstat(fd, st) == -1, "Cannot stat \"%s\"", path); exit_if(!S_ISREG(st->st_mode), "\"%s\" is not a regular file", path); pexit_if(!(*map = mmap(NULL, st->st_size, prot, flags, fd, 0)), "Mmap of \"%s\" failed", path); pexit_if(close(fd) == -1, "Close of %i [%s] failed", fd, path); }
static in_mem_s *get(geoipupdate_s * gu, const char *url) { in_mem_s *mem = in_mem_s_new(); say_if(gu->verbose, "url: %s\n", url); CURL *curl = gu->curl; curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mem_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)mem); common_req(curl, gu); CURLcode res = curl_easy_perform(curl); exit_unless(res == CURLE_OK, "curl_easy_perform() failed: %s\nConnect to %s\n", curl_easy_strerror(res), url); long status = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); exit_if( status < 200 || status >= 300, "Received an unexpected HTTP status code of %ld from %s", status, url); return mem; }
void *xcalloc(size_t nmemb, size_t size) { void *ptr = calloc(nmemb, size); exit_if(!ptr, "Out of memory\n"); return ptr; }
static int gunzip_and_replace(geoipupdate_s * gu, const char *gzipfile, const char *geoip_filename, const char *expected_file_md5) { gzFile gz_fh; FILE *fh = fopen(gzipfile, "rb"); exit_if(NULL == fh, "Can't open %s\n", gzipfile); size_t bsize = 8096; char *buffer = (char *)xmalloc(bsize); ssize_t read_bytes = my_getline(&buffer, &bsize, fh); exit_if(-1 == fclose(fh), "Error closing stream: %s", strerror(errno)); if (read_bytes < 0) { fprintf(stderr, "Read error %s\n", gzipfile); unlink(gzipfile); free(buffer); return ERROR; } const char *no_new_upd = "No new updates available"; if (!strncmp(no_new_upd, buffer, strlen(no_new_upd))) { say_if(gu->verbose, "%s\n", no_new_upd); unlink(gzipfile); free(buffer); return OK; } if (strncmp(buffer, "\x1f\x8b", 2)) { // error not a zip file unlink(gzipfile); fputs(buffer, stderr); return ERROR; } // We do this here as we have to check that there is an update before // we check for the header. exit_unless( 32 == strnlen(expected_file_md5, 33), "Did not receive a valid expected database MD5 from server\n"); char *file_path_test; xasprintf(&file_path_test, "%s.test", geoip_filename); say_if(gu->verbose, "Uncompress file %s to %s\n", gzipfile, file_path_test); gz_fh = gzopen(gzipfile, "rb"); exit_if(gz_fh == NULL, "Can't open %s\n", gzipfile); FILE *fhw = fopen(file_path_test, "wb"); exit_if(fhw == NULL, "Can't open %s\n", file_path_test); for (;; ) { int amt = gzread(gz_fh, buffer, bsize); if (amt == 0) { break; // EOF } exit_if(amt == -1, "Gzip read error while reading from %s\n", gzipfile); exit_unless(fwrite(buffer, 1, amt, fhw) == (size_t)amt, "Gzip write error\n"); } exit_if(-1 == fclose(fhw), "Error closing stream: %s", strerror(errno)); exit_if(gzclose(gz_fh) != Z_OK, "Gzip read error while closing from %s\n", gzipfile); free(buffer); char actual_md5[33]; md5hex(file_path_test, actual_md5); exit_if(strncasecmp(actual_md5, expected_file_md5, 32), "MD5 of new database (%s) does not match expected MD5 (%s)", actual_md5, expected_file_md5); say_if(gu->verbose, "Rename %s to %s\n", file_path_test, geoip_filename); int err = rename(file_path_test, geoip_filename); exit_if(err, "Rename %s to %s failed\n", file_path_test, geoip_filename); // fsync directory to ensure the rename is durable int dirfd = open(gu->database_dir, O_DIRECTORY); exit_if(-1 == dirfd, "Error opening database directory: %s", strerror(errno)); exit_if(-1 == fsync(dirfd), "Error syncing database directory: %s", strerror(errno)); exit_if(-1 == close(dirfd), "Error closing database directory: %s", strerror(errno)); exit_if(-1 == unlink(gzipfile), "Error unlinking %s: %s", gzipfile, strerror(errno)); free(file_path_test); return OK; }
int main(int argc, char *argv[]) { static const char *unlink_paths[] = { "dev/shm", "dev/ptmx", NULL }; static const dir_op_t dirs[] = { dir("dev", 0755), dir("dev/net", 0755), dir("dev/shm", 0755), dir("etc", 0755), dir("proc", 0755), dir("sys", 0755), dir("tmp", 01777), dir("dev/pts", 0755), }; static const char *devnodes[] = { "/dev/null", "/dev/zero", "/dev/full", "/dev/random", "/dev/urandom", "/dev/tty", "/dev/net/tun", "/dev/console", NULL }; static const mount_point mount_table[] = { { "/proc", "/proc", "bind", NULL, MS_BIND|MS_REC }, { "/sys", "/sys", "bind", NULL, MS_BIND|MS_REC }, { "/dev/shm", "/dev/shm", "bind", NULL, MS_BIND }, { "/dev/pts", "/dev/pts", "bind", NULL, MS_BIND }, }; const char *root; int rootfd; char to[4096]; int i; exit_if(argc < 2, "Usage: %s /path/to/root", argv[0]); root = argv[1]; /* Make stage2's root a mount point. Chrooting an application in a * directory which is not a mount point is not nice because the * application would not be able to remount "/" it as private mount. * This allows Docker to run inside rkt. * The recursive flag is to preserve volumes mounted previously by * systemd-nspawn via "rkt run -volume". * */ pexit_if(mount(root, root, "bind", MS_BIND | MS_REC, NULL) == -1, "Make / a mount point failed"); rootfd = open(root, O_DIRECTORY | O_CLOEXEC); pexit_if(rootfd < 0, "Failed to open directory \"%s\"", root); /* Some images have annoying symlinks that are resolved as dangling * links before the chroot in stage1. E.g. "/dev/shm" -> "/run/shm" * Just remove the symlinks. */ for (i = 0; unlink_paths[i]; i++) { pexit_if(unlinkat(rootfd, unlink_paths[i], 0) != 0 && errno != ENOENT && errno != EISDIR, "Failed to unlink \"%s\"", unlink_paths[i]) } /* Create the directories */ umask(0); for (i = 0; i < nelems(dirs); i++) { const dir_op_t *d = &dirs[i]; pexit_if(mkdirat(rootfd, d->name, d->mode) == -1 && errno != EEXIST, "Failed to create directory \"%s/%s\"", root, d->name); } exit_if(!ensure_etc_hosts_exists(root, rootfd), "Failed to ensure \"%s/etc/hosts\" exists", root); close(rootfd); /* systemd-nspawn already creates few /dev entries in the container * namespace: copy_devnodes() * http://cgit.freedesktop.org/systemd/systemd/tree/src/nspawn/nspawn.c?h=v219#n1345 * * But they are not visible by the apps because they are "protected" by * the chroot. * * Bind mount them individually over the chroot border. * * Do NOT bind mount the whole directory /dev because it would shadow * potential individual bind mount by stage0 ("rkt run --volume..."). * * Do NOT use mknod, it would not work for /dev/console because it is * a bind mount to a pts and pts device nodes only work when they live * on a devpts filesystem. */ for (i = 0; devnodes[i]; i++) { const char *from = devnodes[i]; int fd; /* If the file does not exist, skip it. It might be because * the kernel does not provide it (e.g. kernel compiled without * CONFIG_TUN) or because systemd-nspawn does not provide it * (/dev/net/tun is not available with systemd-nspawn < v217 */ if (access(from, F_OK) != 0) continue; exit_if(snprintf(to, sizeof(to), "%s%s", root, from) >= sizeof(to), "Path too long: \"%s\"", to); /* The mode does not matter: it will be bind-mounted over. */ fd = open(to, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0644); if (fd != -1) close(fd); pexit_if(mount(from, to, "bind", MS_BIND, NULL) == -1, "Mounting \"%s\" on \"%s\" failed", from, to); } /* Bind mount directories */ for (i = 0; i < nelems(mount_table); i++) { const mount_point *mnt = &mount_table[i]; exit_if(snprintf(to, sizeof(to), "%s/%s", root, mnt->target) >= sizeof(to), "Path too long: \"%s\"", to); pexit_if(mount(mnt->source, to, mnt->type, mnt->flags, mnt->options) == -1, "Mounting \"%s\" on \"%s\" failed", mnt->source, to); } /* /dev/ptmx -> /dev/pts/ptmx */ exit_if(snprintf(to, sizeof(to), "%s/dev/ptmx", root) >= sizeof(to), "Path too long: \"%s\"", to); pexit_if(symlink("/dev/pts/ptmx", to) == -1, "Failed to create /dev/ptmx symlink"); return EXIT_SUCCESS; }
void *xmalloc(size_t size) { void *ptr = malloc(size); exit_if(!ptr, "Out of memory\n"); return ptr; }
int main(int argc, char *argv[]) { int fd; int pid; pid_t child; int status; int root_fd; exit_if(argc < 4, "Usage: %s pid imageid cmd [args...]", argv[0]) pid = atoi(argv[1]); root_fd = openpidfd(pid, "root"); #define ns(_typ, _nam) \ fd = openpidfd(pid, _nam); \ pexit_if(setns(fd, _typ), "Unable to enter " _nam " namespace"); #if 0 /* TODO(vc): Nspawn isn't employing CLONE_NEWUSER, disabled for now */ ns(CLONE_NEWUSER, "ns/user"); #endif ns(CLONE_NEWIPC, "ns/ipc"); ns(CLONE_NEWUTS, "ns/uts"); ns(CLONE_NEWNET, "ns/net"); ns(CLONE_NEWPID, "ns/pid"); ns(CLONE_NEWNS, "ns/mnt"); pexit_if(fchdir(root_fd) < 0, "Unable to chdir to pod root"); pexit_if(chroot(".") < 0, "Unable to chroot"); pexit_if(close(root_fd) == -1, "Unable to close root_fd"); /* Fork is required to realize consequence of CLONE_NEWPID */ pexit_if(((child = fork()) == -1), "Unable to fork"); /* some stuff make the argv->args copy less cryptic */ #define ENTER_ARGV_FWD_OFFSET 3 #define DIAGEXEC_ARGV_FWD_OFFSET 6 #define args_fwd_idx(_idx) \ ((_idx - ENTER_ARGV_FWD_OFFSET) + DIAGEXEC_ARGV_FWD_OFFSET) if(child == 0) { char root[PATH_MAX]; char env[PATH_MAX]; char *args[args_fwd_idx(argc) + 1 /* NULL terminator */]; int i; /* Child goes on to execute /diagexec */ exit_if(snprintf(root, sizeof(root), "/opt/stage2/%s/rootfs", argv[2]) == sizeof(root), "Root path overflow"); exit_if(snprintf(env, sizeof(env), "/rkt/env/%s", argv[2]) == sizeof(env), "Env path overflow"); args[0] = "/diagexec"; args[1] = root; args[2] = "/"; /* TODO(vc): plumb this into app.WorkingDirectory */ args[3] = env; args[4] = "0"; /* uid */ args[5] = "0"; /* gid */ for(i = ENTER_ARGV_FWD_OFFSET; i < argc; i++) { args[args_fwd_idx(i)] = argv[i]; } args[args_fwd_idx(i)] = NULL; pexit_if(execv(args[0], args) == -1, "Exec failed"); } /* Wait for child, nsenter-like */ for(;;) { if(waitpid(child, &status, WUNTRACED) == pid && (WIFSTOPPED(status))) { kill(getpid(), SIGSTOP); /* the above stops us, upon receiving SIGCONT we'll * continue here and inform our child */ kill(child, SIGCONT); } else { break; } } if(WIFEXITED(status)) { exit(WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) { kill(getpid(), WTERMSIG(status)); } return EXIT_FAILURE; }
static int parse_license_file(geoipupdate_s * up) { say_if(up->verbose, "%s\n", PACKAGE_STRING); FILE *fh = fopen(up->license_file, "rb"); exit_unless(!!fh, "Can't open license file %s\n", up->license_file); say_if(up->verbose, "Opened License file %s\n", up->license_file); const char *sep = " \t\r\n"; size_t bsize = 1024; char *buffer = (char *)xmalloc(bsize); ssize_t read_bytes; while ((read_bytes = my_getline(&buffer, &bsize, fh)) != -1) { size_t idx = strspn(buffer, sep); char *strt = &buffer[idx]; if (*strt == '#') { continue; } if (sscanf(strt, "UserId %d", &up->license.user_id) == 1) { say_if(up->verbose, "UserId %d\n", up->license.user_id); continue; } if (sscanf(strt, "LicenseKey %12s", &up->license.license_key[0]) == 1) { say_if(up->verbose, "LicenseKey %s\n", up->license.license_key); continue; } char *p, *last; if ((p = strtok_r(strt, sep, &last))) { if (!strcmp(p, "ProductIds")) { while ((p = strtok_r(NULL, sep, &last))) { product_insert_once(up, p); } } else if (!strcmp(p, "SkipPeerVerification")) { p = strtok_r(NULL, sep, &last); exit_if(NULL == p || (0 != strcmp(p, "0") && 0 != strcmp(p, "1")), "SkipPeerVerification must be 0 or 1\n"); up->skip_peer_verification = atoi(p); } else if (!strcmp(p, "Protocol")) { p = strtok_r(NULL, sep, &last); exit_if(NULL == p || (0 != strcmp(p, "http") && 0 != strcmp(p, "https")), "Protocol must be http or https\n"); free(up->proto); up->proto = strdup(p); } else if (!strcmp(p, "SkipHostnameVerification")) { p = strtok_r(NULL, sep, &last); exit_if(NULL == p || (0 != strcmp(p, "0") && 0 != strcmp(p, "1")), "SkipHostnameVerification must be 0 or 1\n"); up->skip_hostname_verification = atoi(p); } else if (!strcmp(p, "Host")) { p = strtok_r(NULL, sep, &last); exit_if(NULL == p, "Host must be defined\n"); free(up->host); up->host = strdup(p); } else if (!strcmp(p, "DatabaseDirectory")) { if (!up->do_not_overwrite_database_directory) { p = strtok_r(NULL, sep, &last); exit_if(NULL == p, "DatabaseDirectory must be defined\n"); free(up->database_dir); up->database_dir = strdup(p); } } else if (!strcmp(p, "Proxy")) { p = strtok_r(NULL, sep, &last); exit_if(NULL == p, "Proxy must be defined 1.2.3.4:12345\n"); free(up->proxy); up->proxy = strdup(p); } else if (!strcmp(p, "ProxyUserPassword")) { p = strtok_r(NULL, sep, &last); exit_if(NULL == p, "ProxyUserPassword must be defined xyz:abc\n"); free(up->proxy_user_password); up->proxy_user_password = strdup(p); } else { say_if(up->verbose, "Skip unknown directive: %s\n", p); } } } free(buffer); exit_if(-1 == fclose(fh), "Error closing stream: %s", strerror(errno)); say_if(up->verbose, "Read in license key %s\nNumber of product ids %d\n", up->license_file, product_count(up)); return 1; }
void *xrealloc(void *ptr, size_t size) { void *mem = realloc(ptr, size); exit_if(mem == NULL, "Out of memory\n"); return mem; }
static void diag(const char *exe) { static const uint8_t elf[] = {0x7f, 'E', 'L', 'F'}; static const uint8_t shebang[] = {'#','!'}; static int diag_depth; struct stat st; const uint8_t *mm; const char *itrp = NULL; map_file(exe, PROT_READ, MAP_SHARED, &st, (void **)&mm); exit_if(!((S_IXUSR|S_IXGRP|S_IXOTH) & st.st_mode), "\"%s\" is not executable", exe) if(st.st_size >= sizeof(shebang) && !memcmp(mm, shebang, sizeof(shebang))) { const uint8_t *nl; int maxlen = MIN(PATH_MAX, st.st_size - sizeof(shebang)); /* TODO(vc): EOF-terminated shebang lines are technically possible */ exit_if(!(nl = memchr(&mm[sizeof(shebang)], '\n', maxlen)), "Shebang line too long"); pexit_if(!(itrp = strndup((char *)&mm[sizeof(shebang)], (nl - mm) - 2)), "Failed to dup interpreter path"); } else if(st.st_size >= sizeof(elf) && !memcmp(mm, elf, sizeof(elf))) { uint64_t (*lget)(const uint8_t *) = NULL; uint32_t (*iget)(const uint8_t *) = NULL; uint16_t (*sget)(const uint8_t *) = NULL; const void *phoff = NULL, *phesz = NULL, *phecnt = NULL; const uint8_t *ph = NULL; int i, phreloff, phrelsz; exit_if(mm[ELF_VERSION] != 1, "Unsupported ELF version: %hhx", mm[ELF_VERSION]); /* determine which accessors to use and where */ if(mm[ELF_BITS] == ELF_BITS_32) { if(mm[ELF_ENDIAN] == ELF_ENDIAN_LITL) { lget = le32_lget; sget = le_sget; iget = le_iget; } else if(mm[ELF_ENDIAN] == ELF_ENDIAN_BIG) { lget = be32_lget; sget = be_sget; iget = be_iget; } phoff = &mm[ELF32_PHT_OFF]; phesz = &mm[ELF32_PHTE_SIZE]; phecnt = &mm[ELF32_PHTE_CNT]; phreloff = ELF32_PHE_OFF; phrelsz = ELF32_PHE_SIZE; } else if(mm[ELF_BITS] == ELF_BITS_64) { if(mm[ELF_ENDIAN] == ELF_ENDIAN_LITL) { lget = le64_lget; sget = le_sget; iget = le_iget; } else if(mm[ELF_ENDIAN] == ELF_ENDIAN_BIG) { lget = be64_lget; sget = be_sget; iget = be_iget; } phoff = &mm[ELF64_PHT_OFF]; phesz = &mm[ELF64_PHTE_SIZE]; phecnt = &mm[ELF64_PHTE_CNT]; phreloff = ELF64_PHE_OFF; phrelsz = ELF64_PHE_SIZE; } exit_if(!lget, "Unsupported ELF format"); if(!phoff) /* program header may be absent, don't make it an error */ return; /* TODO(vc): sanity checks on values before using them */ for(ph = &mm[lget(phoff)], i = 0; i < sget(phecnt); i++, ph += sget(phesz)) { if(iget(ph) == ELF_PT_INTERP) { itrp = strndup((char *)&mm[lget(&ph[phreloff])], lget(&ph[phrelsz])); break; } } } else { exit_if(1, "Unsupported file type"); } exit_if(!itrp, "Unable to determine interpreter for \"%s\"", exe); exit_if(*itrp != '/', "Path must be absolute: \"%s\"", itrp); exit_if(++diag_depth > MAX_DIAG_DEPTH, "Excessive interpreter recursion, giving up"); diag(itrp); }