mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-13 03:51:10 +00:00
466 lines
21 KiB
C
466 lines
21 KiB
C
// Part of libddnatv-hook.so, included by ddnatv-hook.c (one translation unit, so the
|
|
// parts share their static functions and are built with the same command as before).
|
|
//
|
|
// Static Go programs: they make their system calls themselves, so Ubuntu's paths mean nothing to
|
|
// them. The code of gosys/gosys.c is added to their file.
|
|
|
|
// ---- Static Go programs -------------------------------------------------------------------
|
|
// A Go program built without cgo (all of Ubuntu's, and the single files downloaded from a project's
|
|
// releases) has no C library: the preloaded hook is never loaded into it, so "/etc/resolv.conf" is
|
|
// a file of Android's that does not exist, and so is every program it starts. Go makes all of its
|
|
// system calls but a few of its runtime's in one assembly function, internal/runtime/syscall.
|
|
// Syscall6, which loads the number and the arguments from the stack and executes "svc #0".
|
|
//
|
|
// Before such a program (AArch64, Go 1.19 or newer) starts, The native userland changes its file in place, once:
|
|
// - the code of gosys/gosys.c is put after the program's code, in the zero padding that the Go
|
|
// linker leaves between its segments (the segment is made longer, and executable if it only
|
|
// was readable),
|
|
// - zero-filled memory for it is added to the end of the program's data segment,
|
|
// - the "svc #0" of Syscall6 becomes a branch to a few instructions in that code, which call it
|
|
// and go back to the instruction after the "svc".
|
|
// The code maps the paths of the calls into the rootfs, as the hook does for C programs, and starts
|
|
// the programs of the rootfs through the hook. The file keeps its size, its times and its mode;
|
|
// only the program headers, the padding and one instruction differ. The file is changed back and
|
|
// patched again when the code of a newer The native userland differs (the trailer says what to undo).
|
|
|
|
#ifdef DDNATV_HAVE_GOSYS
|
|
|
|
#define GO_MAX_PH 32
|
|
#define GO_PF_X 1u
|
|
#define GO_PF_W 2u
|
|
#define GO_ET_EXEC 2
|
|
#define GO_ET_DYN 3
|
|
|
|
struct go_ph {
|
|
uint32_t type, flags;
|
|
uint64_t offset, vaddr, filesz, memsz;
|
|
uint64_t pos; // where the entry is in the file
|
|
};
|
|
|
|
static uint64_t go_rd64(const unsigned char *p) { return (uint64_t)rd32(p) | ((uint64_t)rd32(p + 4) << 32); }
|
|
static void go_wr64(unsigned char *p, uint64_t v) { wr32(p, (uint32_t)v); wr32(p + 4, (uint32_t)(v >> 32)); }
|
|
|
|
// The program headers of a 64-bit little-endian ELF; their number, or -1
|
|
static int go_read_phdrs(int fd, struct go_ph *ph, uint16_t *etype, uint16_t *machine) {
|
|
unsigned char eh[64];
|
|
if (pread(fd, eh, sizeof(eh), 0) != (ssize_t)sizeof(eh) || memcmp(eh, "\x7f" "ELF", 4) != 0 || eh[4] != 2 || eh[5] != 1)
|
|
return -1;
|
|
*etype = rd16(eh + 16);
|
|
*machine = rd16(eh + 18);
|
|
uint64_t phoff = go_rd64(eh + 32);
|
|
uint16_t phentsize = rd16(eh + 54), phnum = rd16(eh + 56);
|
|
if (phentsize != 56 || phnum == 0 || phnum > GO_MAX_PH) return -1;
|
|
for (int i = 0; i < phnum; i++) {
|
|
unsigned char e[56];
|
|
uint64_t pos = phoff + (uint64_t)i * 56;
|
|
if (pread(fd, e, sizeof(e), (off_t)pos) != (ssize_t)sizeof(e)) return -1;
|
|
ph[i].type = rd32(e);
|
|
ph[i].flags = rd32(e + 4);
|
|
ph[i].offset = go_rd64(e + 8);
|
|
ph[i].vaddr = go_rd64(e + 16);
|
|
ph[i].filesz = go_rd64(e + 32);
|
|
ph[i].memsz = go_rd64(e + 40);
|
|
ph[i].pos = pos;
|
|
}
|
|
return phnum;
|
|
}
|
|
|
|
// The instructions of Syscall6 up to its system call: x8 and x0 to x5 loaded from the stack
|
|
// (ldr x8, [sp, #8]; ldr x0, [sp, #16] ... ldr x5, [sp, #56]), then "svc #0" and "cmn x0, #4095"
|
|
static const uint32_t k_go_syscall6[7] = {
|
|
0xf94007e8u, 0xf9400be0u, 0xf9400fe1u, 0xf94013e2u, 0xf94017e3u, 0xf9401be4u, 0xf9401fe5u,
|
|
};
|
|
#define GO_SVC 0xd4000001u
|
|
#define GO_CMN_X0_4095 0xb13ffc1fu
|
|
|
|
// Where Syscall6 makes its call: the file offset of the instruction after the seven loads (the
|
|
// "svc #0", or the branch that replaced it). *found counts the places; the first is returned.
|
|
static int64_t go_find_syscall6(int fd, const struct go_ph *ph, int n, int *found) {
|
|
int64_t first = -1;
|
|
*found = 0;
|
|
enum { CHUNK = 1 << 20, KEEP = 36 };
|
|
unsigned char *buf = (unsigned char *)malloc(CHUNK + KEEP);
|
|
if (!buf) return -1;
|
|
for (int i = 0; i < n; i++) {
|
|
if (ph[i].type != 1 /* PT_LOAD */ || !(ph[i].flags & GO_PF_X) || ph[i].filesz == 0) continue;
|
|
uint64_t pos = 0;
|
|
while (pos < ph[i].filesz) {
|
|
uint64_t want = ph[i].filesz - pos;
|
|
if (want > CHUNK + KEEP) want = CHUNK + KEEP;
|
|
ssize_t got = pread(fd, buf, (size_t)want, (off_t)(ph[i].offset + pos));
|
|
if (got <= KEEP) break;
|
|
for (ssize_t o = 0; o + 36 <= got; o += 4) {
|
|
if (rd32(buf + o) != k_go_syscall6[0]) continue;
|
|
int same = 1;
|
|
for (int k = 1; k < 7 && same; k++) same = rd32(buf + o + 4 * k) == k_go_syscall6[k];
|
|
if (!same || rd32(buf + o + 32) != GO_CMN_X0_4095) continue;
|
|
if (*found == 0) first = (int64_t)(ph[i].offset + pos + (uint64_t)o + 28);
|
|
(*found)++;
|
|
}
|
|
if (pos + (uint64_t)got >= ph[i].filesz) break;
|
|
pos += (uint64_t)got - (KEEP - 4); // the next chunk starts where the last place of this could begin, no place is split or counted twice
|
|
}
|
|
}
|
|
free(buf);
|
|
return first;
|
|
}
|
|
|
|
// "b <delta>" / "bl <delta>" for a distance in bytes, or 0 when it does not fit
|
|
static uint32_t go_branch(uint32_t base, int64_t delta) {
|
|
if ((delta & 3) != 0 || delta >= (1LL << 27) || delta < -(1LL << 27)) return 0;
|
|
return base | (uint32_t)((delta >> 2) & 0x3ffffff);
|
|
}
|
|
|
|
static uint64_t go_align_up(uint64_t v, uint64_t a) { return (v + a - 1) & ~(a - 1); }
|
|
|
|
// The strings the added code needs, as they are now
|
|
static int go_make_header(struct gosys_hdr *h) {
|
|
if (g_ddnatv_root_len == 0 || g_ddnatv_root_len >= GOSYS_ROOT_MAX) return -1;
|
|
memcpy(h->root, g_ddnatv_root, g_ddnatv_root_len);
|
|
h->root_len = (uint32_t)g_ddnatv_root_len;
|
|
|
|
char buf[PATH_MAX];
|
|
if (find_dynamic_linker_for_machine(g_ddnatv_root, EM_AARCH64, buf, sizeof(buf)) && strlen(buf) < GOSYS_PATH_MAX) {
|
|
h->ldso_len = (uint32_t)strlen(buf);
|
|
memcpy(h->ldso, buf, h->ldso_len);
|
|
}
|
|
static const char *const hooks[] = { "/usr/lib/libddnatv-hook.so", "/lib/libddnatv-hook.so" };
|
|
for (size_t i = 0; i < sizeof(hooks) / sizeof(hooks[0]); i++) {
|
|
size_t n = ddnatv_append(buf, sizeof(buf), 0, g_ddnatv_root, g_ddnatv_root_len);
|
|
n = ddnatv_append(buf, sizeof(buf), n, hooks[i], strlen(hooks[i]));
|
|
if (n < GOSYS_PATH_MAX && access(buf, F_OK) == 0) {
|
|
h->hook_len = (uint32_t)n;
|
|
memcpy(h->hook, buf, n);
|
|
break;
|
|
}
|
|
}
|
|
// The program that starts the others: bash, which can give them the argv[0] they are asked for
|
|
static const char *const runners[] = { "/usr/bin/bash", "/bin/bash", "/usr/bin/env", "/bin/env" };
|
|
for (size_t i = 0; i < sizeof(runners) / sizeof(runners[0]); i++) {
|
|
size_t n = ddnatv_append(buf, sizeof(buf), 0, g_ddnatv_root, g_ddnatv_root_len);
|
|
n = ddnatv_append(buf, sizeof(buf), n, runners[i], strlen(runners[i]));
|
|
if (n < GOSYS_PATH_MAX && access(buf, F_OK) == 0) {
|
|
h->runner_len = (uint32_t)n;
|
|
memcpy(h->runner, buf, n);
|
|
if (i < 2) h->flags |= GOSYS_FLAG_RUNNER_IS_BASH;
|
|
break;
|
|
}
|
|
}
|
|
// The directories the programs of an AArch64 rootfs load their libraries from
|
|
static const int dirs[] = { 0, 1, 4, 5, 6 };
|
|
size_t used = 0;
|
|
for (size_t i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) {
|
|
const char *d = k_rootfs_lib_dirs[dirs[i]];
|
|
if (i > 0) used = ddnatv_append(buf, sizeof(buf), used, ":", 1);
|
|
used = ddnatv_append(buf, sizeof(buf), used, g_ddnatv_root, g_ddnatv_root_len);
|
|
used = ddnatv_append(buf, sizeof(buf), used, d, strlen(d));
|
|
}
|
|
if (used < GOSYS_LIBS_MAX) {
|
|
h->libs_len = (uint32_t)used;
|
|
memcpy(h->libs, buf, used);
|
|
}
|
|
if (h->ldso_len == 0 || h->runner_len == 0) h->runner_len = 0; // Ubuntu programs cannot be started
|
|
return 0;
|
|
}
|
|
|
|
static int go_pwrite_all(int fd, const void *buf, size_t len, uint64_t off) {
|
|
return pwrite(fd, buf, len, (off_t)off) == (ssize_t)len ? 0 : -1;
|
|
}
|
|
|
|
static int go_open_for_write(int fd, const struct stat *st, const char *path, int *made_writable) {
|
|
int wfd = open(path, O_WRONLY | O_CLOEXEC);
|
|
if (wfd < 0 && errno == EACCES && fchmod(fd, (st->st_mode | S_IWUSR) & 07777) == 0) {
|
|
*made_writable = 1;
|
|
wfd = open(path, O_WRONLY | O_CLOEXEC);
|
|
}
|
|
return wfd; // ETXTBSY: the program is running right now, patched at its next start
|
|
}
|
|
|
|
// The bytes of the added code for a given place: the code with its header (the strings, and where
|
|
// the scratch memory is), the instructions that connect the call site to it, and the trailer that
|
|
// says what was changed to put it there.
|
|
static void go_build_image(unsigned char *img, const struct gosys_hdr *strings, uint64_t cave_v, uint64_t scratch_v,
|
|
uint32_t to_entry, uint32_t back, const struct gosys_trailer *info) {
|
|
memcpy(img, gosys_blob, GOSYS_BLOB_SIZE);
|
|
struct gosys_hdr h = *strings;
|
|
h.scratch_rel = (int64_t)scratch_v - (int64_t)cave_v;
|
|
memcpy(img, &h, sizeof(h));
|
|
unsigned char *th = img + GOSYS_THUNKS_OFF;
|
|
wr32(th + 0, 0xd10043ffu); // sub sp, sp, #16
|
|
wr32(th + 4, 0xf90003feu); // str x30, [sp]
|
|
wr32(th + 8, 0xaa0803e7u); // mov x7, x8 (the number is the eighth argument)
|
|
wr32(th + 12, to_entry); // bl entry
|
|
wr32(th + 16, 0xf94003feu); // ldr x30, [sp]
|
|
wr32(th + 20, 0x910043ffu); // add sp, sp, #16
|
|
wr32(th + 24, back); // b <the instruction after the call>
|
|
struct gosys_trailer t;
|
|
memcpy(&t, img + GOSYS_BLOB_SIZE - sizeof(t), sizeof(t));
|
|
t.version = GOSYS_VERSION;
|
|
t.total_size = GOSYS_BLOB_SIZE;
|
|
t.text_orig_filesz = info->text_orig_filesz;
|
|
t.text_orig_flags = info->text_orig_flags;
|
|
t.rw_orig_memsz = info->rw_orig_memsz;
|
|
t.text_index = info->text_index;
|
|
t.rw_index = info->rw_index;
|
|
t.site_file_off = info->site_file_off;
|
|
memcpy(img + GOSYS_BLOB_SIZE - sizeof(t), &t, sizeof(t));
|
|
}
|
|
|
|
// A short fingerprint of the added code, so that a program patched by an older The native userland is looked at
|
|
// again (the marker of a prepared program has it)
|
|
static uint32_t go_blob_fingerprint(void) {
|
|
static uint32_t fp;
|
|
if (fp == 0) {
|
|
uint32_t h = 2166136261u;
|
|
for (size_t i = 0; i < GOSYS_BLOB_SIZE; i++) h = (h ^ gosys_blob[i]) * 16777619u;
|
|
fp = h ? h : 1;
|
|
}
|
|
return fp;
|
|
}
|
|
|
|
// Gives the program its old segments and call back: the added code is zeroed, the data segment
|
|
// is shortened, the instruction is a "svc #0" again.
|
|
static int go_undo(int wfd, struct go_ph *ph, int cave_seg, const struct gosys_trailer *tr, int64_t site_off) {
|
|
uint64_t cave_off = ph[cave_seg].offset + ph[cave_seg].filesz - tr->total_size;
|
|
unsigned char word[4];
|
|
wr32(word, GO_SVC);
|
|
if (go_pwrite_all(wfd, word, 4, (uint64_t)site_off) != 0) return -1;
|
|
unsigned char sz[16];
|
|
go_wr64(sz, tr->text_orig_filesz);
|
|
go_wr64(sz + 8, tr->text_orig_filesz);
|
|
if (go_pwrite_all(wfd, sz, 16, ph[cave_seg].pos + 32) != 0) return -1;
|
|
wr32(word, tr->text_orig_flags);
|
|
if (go_pwrite_all(wfd, word, 4, ph[cave_seg].pos + 4) != 0) return -1;
|
|
if (tr->rw_index < GO_MAX_PH) {
|
|
unsigned char mem[8];
|
|
go_wr64(mem, tr->rw_orig_memsz);
|
|
if (go_pwrite_all(wfd, mem, 8, ph[tr->rw_index].pos + 40) != 0) return -1;
|
|
}
|
|
unsigned char *zero = (unsigned char *)calloc(1, tr->total_size);
|
|
if (!zero) return -1;
|
|
int rc = go_pwrite_all(wfd, zero, tr->total_size, cave_off);
|
|
free(zero);
|
|
return rc;
|
|
}
|
|
|
|
// DDNATV_NO_GOSYS=1 (any value but empty or 0): no code is added to Go programs, and the code that is
|
|
// in one is taken out again, so that a program can be run as it was built, to see whether a fault
|
|
// is the program's or the code's. The marker of a prepared program says it (static_marker).
|
|
static int go_disabled(void) {
|
|
const char *v = getenv("DDNATV_NO_GOSYS");
|
|
return v && v[0] != '\0' && strcmp(v, "0") != 0;
|
|
}
|
|
|
|
// The three branches that connect the call site and the code: site -> thunk, thunk -> entry (bl),
|
|
// thunk -> the instruction after the site. 0 when one of them is too far.
|
|
static int go_branches(uint64_t cave_v, uint64_t site_v, uint32_t *to_thunk, uint32_t *to_entry, uint32_t *back) {
|
|
uint64_t thunk_v = cave_v + GOSYS_THUNKS_OFF;
|
|
*to_thunk = go_branch(0x14000000u, (int64_t)thunk_v - (int64_t)site_v);
|
|
*to_entry = go_branch(0x94000000u, (int64_t)(cave_v + GOSYS_ENTRY_OFF) - (int64_t)(thunk_v + 12));
|
|
*back = go_branch(0x14000000u, (int64_t)(site_v + 4) - (int64_t)(thunk_v + 24));
|
|
return *to_thunk && *to_entry && *back;
|
|
}
|
|
|
|
// Adds the code to the program, or brings it up to date. Returns 1 when it changed the file, 0 when
|
|
// there was nothing to do (not a Go program of a kind the code can serve, or already done), -1 when
|
|
// the file could not be changed (it is started as it is).
|
|
static int prepare_go_program(int fd, const char *path, const struct stat *st) {
|
|
struct go_ph ph[GO_MAX_PH];
|
|
uint16_t etype = 0, machine = 0;
|
|
int n = go_read_phdrs(fd, ph, &etype, &machine);
|
|
if (n < 0) return -1;
|
|
if (machine != EM_AARCH64 || (etype != GO_ET_EXEC && etype != GO_ET_DYN)) return 0;
|
|
|
|
int found = 0;
|
|
int64_t site_off = go_find_syscall6(fd, ph, n, &found);
|
|
if (found != 1) return 0; // not a Go of 1.19 or newer (or its function is not there once)
|
|
|
|
// The loadable segments, by address
|
|
int order[GO_MAX_PH], nl = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
if (ph[i].type != 1 /* PT_LOAD */) continue;
|
|
int k = nl++;
|
|
while (k > 0 && ph[order[k - 1]].vaddr > ph[i].vaddr) {
|
|
order[k] = order[k - 1];
|
|
k--;
|
|
}
|
|
order[k] = i;
|
|
}
|
|
if (nl < 2) return 0;
|
|
|
|
// The call site must be in a segment that is mapped as code
|
|
uint64_t site_v = 0;
|
|
for (int i = 0; i < nl; i++) {
|
|
const struct go_ph *s = &ph[order[i]];
|
|
if ((s->flags & GO_PF_X) && (uint64_t)site_off >= s->offset && (uint64_t)site_off < s->offset + s->filesz)
|
|
site_v = s->vaddr + ((uint64_t)site_off - s->offset);
|
|
}
|
|
if (site_v == 0) return 0;
|
|
|
|
struct gosys_hdr strings;
|
|
memset(&strings, 0, sizeof(strings));
|
|
memcpy(strings.magic, "CXGOSYS1", 8);
|
|
strings.version = GOSYS_VERSION;
|
|
if (go_make_header(&strings) != 0) return 0;
|
|
|
|
unsigned char *img = (unsigned char *)malloc(GOSYS_BLOB_SIZE);
|
|
unsigned char *have = (unsigned char *)malloc(GOSYS_BLOB_SIZE);
|
|
if (!img || !have) {
|
|
free(img);
|
|
free(have);
|
|
return -1;
|
|
}
|
|
int wfd = -1, made_writable = 0, changed = 0, rc = 0;
|
|
unsigned char word[4];
|
|
|
|
// An earlier run's code: it ends the segment it is in, and says what it changed
|
|
int cave_seg = -1;
|
|
struct gosys_trailer tr;
|
|
memset(&tr, 0, sizeof(tr));
|
|
for (int i = 0; i < nl && cave_seg < 0; i++) {
|
|
const struct go_ph *s = &ph[order[i]];
|
|
if (s->filesz < sizeof(tr) + sizeof(struct gosys_hdr)) continue;
|
|
struct gosys_trailer t;
|
|
if (pread(fd, &t, sizeof(t), (off_t)(s->offset + s->filesz - sizeof(t))) != (ssize_t)sizeof(t)) continue;
|
|
if (memcmp(t.magic, "CXGOEND1", 8) != 0 || t.total_size > s->filesz || t.total_size < sizeof(t) + sizeof(struct gosys_hdr)) continue;
|
|
tr = t;
|
|
cave_seg = order[i];
|
|
}
|
|
|
|
if (go_disabled()) {
|
|
if (cave_seg >= 0) {
|
|
wfd = go_open_for_write(fd, st, path, &made_writable);
|
|
if (wfd < 0) {
|
|
rc = -1;
|
|
} else {
|
|
rc = go_undo(wfd, ph, cave_seg, &tr, site_off);
|
|
changed = rc == 0;
|
|
}
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
if (cave_seg >= 0) {
|
|
uint64_t cave_off = ph[cave_seg].offset + ph[cave_seg].filesz - tr.total_size;
|
|
uint64_t cave_v = ph[cave_seg].vaddr + (cave_off - ph[cave_seg].offset);
|
|
int same = 0, site_ok = 0;
|
|
uint32_t to_thunk = 0, to_entry = 0, back = 0;
|
|
// Is it what would be put there now? (the code of an older The native userland differs)
|
|
if (tr.total_size == GOSYS_BLOB_SIZE && tr.rw_index < (uint32_t)n && ph[tr.rw_index].type == 1 &&
|
|
go_branches(cave_v, site_v, &to_thunk, &to_entry, &back)) {
|
|
uint64_t scratch_v = go_align_up(ph[tr.rw_index].vaddr + tr.rw_orig_memsz, 64);
|
|
go_build_image(img, &strings, cave_v, scratch_v, to_entry, back, &tr);
|
|
same = pread(fd, have, GOSYS_BLOB_SIZE, (off_t)cave_off) == (ssize_t)GOSYS_BLOB_SIZE &&
|
|
memcmp(have, img, GOSYS_BLOB_SIZE) == 0 &&
|
|
ph[tr.rw_index].memsz == scratch_v - ph[tr.rw_index].vaddr + GOSYS_SCRATCH_SIZE;
|
|
}
|
|
unsigned char cur[4];
|
|
site_ok = same && pread(fd, cur, 4, (off_t)site_off) == 4 && rd32(cur) == to_thunk;
|
|
if (same && site_ok) goto done;
|
|
|
|
wfd = go_open_for_write(fd, st, path, &made_writable);
|
|
if (wfd < 0) {
|
|
rc = -1;
|
|
goto done;
|
|
}
|
|
if (same) {
|
|
// The code is there, only the branch is not (a patch that was cut short)
|
|
wr32(word, to_thunk);
|
|
rc = go_pwrite_all(wfd, word, 4, (uint64_t)site_off);
|
|
changed = rc == 0;
|
|
goto done;
|
|
}
|
|
// An older code, or other strings: back to the plain program, then patch again
|
|
if (go_undo(wfd, ph, cave_seg, &tr, site_off) != 0) {
|
|
rc = -1;
|
|
goto done;
|
|
}
|
|
close(wfd);
|
|
wfd = -1;
|
|
if (made_writable) fchmod(fd, st->st_mode & 07777);
|
|
free(img);
|
|
free(have);
|
|
return prepare_go_program(fd, path, st);
|
|
}
|
|
|
|
// A place for the code: the end of a segment that is followed by padding up to the next one
|
|
uint64_t total = GOSYS_BLOB_SIZE;
|
|
int text = -1;
|
|
uint64_t cave_v = 0, cave_off = 0;
|
|
for (int pass = 0; pass < 2 && text < 0; pass++) {
|
|
for (int k = 0; k + 1 < nl && text < 0; k++) {
|
|
const struct go_ph *s = &ph[order[k]], *nx = &ph[order[k + 1]];
|
|
if (pass == 0 ? !(s->flags & GO_PF_X) : (s->flags & GO_PF_W)) continue;
|
|
if (s->memsz != s->filesz || s->filesz == 0) continue;
|
|
uint64_t v = go_align_up(s->vaddr + s->filesz, 64);
|
|
uint64_t off = s->offset + (v - s->vaddr);
|
|
if (v + total > nx->vaddr || (nx->vaddr & 0x3fff) != 0) continue;
|
|
if (off + total > nx->offset || off + total > (uint64_t)st->st_size) continue;
|
|
// Only padding may be written over
|
|
uint64_t pad_from = s->offset + s->filesz, pad_len = off + total - pad_from;
|
|
unsigned char *pad = (unsigned char *)malloc(pad_len);
|
|
if (!pad) continue;
|
|
int zero = pread(fd, pad, pad_len, (off_t)pad_from) == (ssize_t)pad_len;
|
|
for (uint64_t i = 0; zero && i < pad_len; i++) zero = pad[i] == 0;
|
|
free(pad);
|
|
if (!zero) continue;
|
|
text = order[k];
|
|
cave_v = v;
|
|
cave_off = off;
|
|
}
|
|
}
|
|
int rw = order[nl - 1];
|
|
uint32_t to_thunk = 0, to_entry = 0, back = 0;
|
|
if (text < 0 || !(ph[rw].flags & GO_PF_W) || rw == text || !go_branches(cave_v, site_v, &to_thunk, &to_entry, &back)) goto done;
|
|
|
|
uint64_t scratch_v = go_align_up(ph[rw].vaddr + ph[rw].memsz, 64);
|
|
struct gosys_trailer info;
|
|
memset(&info, 0, sizeof(info));
|
|
info.text_orig_filesz = ph[text].filesz;
|
|
info.text_orig_flags = ph[text].flags;
|
|
info.rw_orig_memsz = ph[rw].memsz;
|
|
info.text_index = (uint32_t)text;
|
|
info.rw_index = (uint32_t)rw;
|
|
info.site_file_off = (uint64_t)site_off;
|
|
go_build_image(img, &strings, cave_v, scratch_v, to_entry, back, &info);
|
|
|
|
wfd = go_open_for_write(fd, st, path, &made_writable);
|
|
if (wfd < 0) {
|
|
rc = -1;
|
|
goto done;
|
|
}
|
|
// The code first and the call site last: whatever is cut short leaves a program that runs
|
|
unsigned char sz[16];
|
|
unsigned char mem[8];
|
|
go_wr64(mem, scratch_v - ph[rw].vaddr + GOSYS_SCRATCH_SIZE);
|
|
go_wr64(sz, cave_v + GOSYS_BLOB_SIZE - ph[text].vaddr);
|
|
go_wr64(sz + 8, cave_v + GOSYS_BLOB_SIZE - ph[text].vaddr);
|
|
rc = go_pwrite_all(wfd, img, GOSYS_BLOB_SIZE, cave_off);
|
|
if (rc == 0) rc = go_pwrite_all(wfd, mem, 8, ph[rw].pos + 40);
|
|
if (rc == 0) rc = go_pwrite_all(wfd, sz, 16, ph[text].pos + 32);
|
|
if (rc == 0 && !(ph[text].flags & GO_PF_X)) {
|
|
wr32(word, ph[text].flags | GO_PF_X);
|
|
rc = go_pwrite_all(wfd, word, 4, ph[text].pos + 4);
|
|
}
|
|
if (rc == 0) {
|
|
wr32(word, to_thunk);
|
|
rc = go_pwrite_all(wfd, word, 4, (uint64_t)site_off);
|
|
}
|
|
changed = rc == 0;
|
|
|
|
done:
|
|
free(img);
|
|
free(have);
|
|
if (wfd >= 0) {
|
|
// Keep the times: make and friends must not see a rebuilt program.
|
|
const struct timespec times[2] = { st->st_atim, st->st_mtim };
|
|
futimens(wfd, times);
|
|
close(wfd);
|
|
}
|
|
if (made_writable) fchmod(fd, st->st_mode & 07777);
|
|
if (rc != 0) return -1;
|
|
return changed ? 1 : 0;
|
|
}
|
|
|
|
#endif /* DDNATV_HAVE_GOSYS */
|