Skip to content

Commit 2a11a09

Browse files
authored
Remove realpath/getcwd from loaders (#1024)
This implements proposals 1 and 2a from this gist: https://gist.github.com/mrdomino/2222cab61715fd527e82e036ba4156b1 The only reason to use realpath from the loader was to try to prevent a TOCTOU between the loader and the binary. But this is only a real issue in set-id contexts, and in those cases there is already a canonical way to do it: `/dev/fd`, passed by the kernel to the loader, so all we have to do is pass that along to the binary. Aside from realpath, there is no reason to absolutize the path we supply to the binary, since it can call `getcwd` as well as we can, and on non- M1 the binary is in a much better position to make that call. Since we no longer absolutize the path, the binary does need to do this, so we make its argv-parsing code generic and apply that to the different possible places the path could come from. This means that `_` is finally usable as a relative path, as a nice side benefit. The M1 realpath code had a significant bug - it uses the wrong offset to truncate the `.ape` in the `$prog.ape` case. This PR also fixes a regression in `ape $progname` out of `$PATH` on the two BSDs (Free and Net) that did not implement `RealPath`.
1 parent f73576a commit 2a11a09

File tree

3 files changed

+69
-96
lines changed

3 files changed

+69
-96
lines changed

ape/ape-m1.c

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -316,21 +316,12 @@ __attribute__((__noreturn__)) static void Pexit(const char *c, int failed,
316316
}
317317

318318
static char AccessCommand(struct PathSearcher *ps, unsigned long pathlen) {
319-
char buf[PATH_MAX];
320-
size_t n;
321319
if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) {
322320
return 0;
323321
}
324322
if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/';
325323
memmove(ps->path + pathlen, ps->name, ps->namelen);
326324
ps->path[pathlen + ps->namelen] = 0;
327-
if (!realpath(ps->path, buf)) {
328-
Pexit(ps->path, -errno, "realpath");
329-
}
330-
if ((n = strlen(buf)) >= sizeof(ps->path)) {
331-
Pexit(buf, 0, "too long");
332-
}
333-
memcpy(ps->path, buf, n + 1);
334325
if (!access(ps->path, X_OK)) {
335326
if (ps->indirect) {
336327
ps->namelen -= 4;

ape/loader.c

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -545,40 +545,6 @@ __attribute__((__noreturn__)) static void Pexit(int os, const char *c, int rc,
545545
Exit(127, os);
546546
}
547547

548-
#define PSFD "/proc/self/fd/"
549-
550-
static int RealPath(int os, int fd, char *path, char **resolved) {
551-
char buf[PATH_MAX];
552-
int rc;
553-
if (IsLinux()) {
554-
char psfd[sizeof(PSFD) + 19];
555-
MemMove(psfd, PSFD, sizeof(PSFD) - 1);
556-
Utoa(psfd + sizeof(PSFD) - 1, fd);
557-
rc = SystemCall(-100, (long)psfd, (long)buf, PATH_MAX, 0, 0, 0,
558-
IsAarch64() ? 78 : 267);
559-
if (rc >= 0) {
560-
if (rc == PATH_MAX) {
561-
rc = -36;
562-
} else {
563-
buf[rc] = 0;
564-
}
565-
}
566-
} else if (IsXnu()) {
567-
rc = SystemCall(fd, 50, (long)buf, 0, 0, 0, 0, 92 | 0x2000000);
568-
} else if (IsOpenbsd()) {
569-
rc = SystemCall((long)path, (long)buf, 0, 0, 0, 0, 0, 115);
570-
} else {
571-
*resolved = 0;
572-
return 0;
573-
}
574-
if (rc >= 0) {
575-
MemMove(path, buf, StrLen(buf) + 1);
576-
*resolved = path;
577-
rc = 0;
578-
}
579-
return rc;
580-
}
581-
582548
static char AccessCommand(struct PathSearcher *ps, unsigned long pathlen) {
583549
if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) return 0;
584550
if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/';
@@ -644,9 +610,8 @@ static char *Commandv(struct PathSearcher *ps, int os, const char *name,
644610
}
645611
}
646612

647-
__attribute__((__noreturn__)) static void Spawn(int os, const char *exe,
648-
char *path, int fd, long *sp,
649-
unsigned long pagesz,
613+
__attribute__((__noreturn__)) static void Spawn(int os, char *exe, int fd,
614+
long *sp, unsigned long pagesz,
650615
struct ElfEhdr *e,
651616
struct ElfPhdr *p) {
652617
long rc;
@@ -803,12 +768,12 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe,
803768
Msyscall(dynbase + code, codesize, os);
804769

805770
/* call program entrypoint */
806-
Launch(IsFreebsd() ? sp : 0, dynbase + e->e_entry, path, sp, os);
771+
Launch(IsFreebsd() ? sp : 0, dynbase + e->e_entry, exe, sp, os);
807772
}
808773

809774
static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf,
810-
const char *exe, char *path, int fd, long *sp,
811-
long *auxv, unsigned long pagesz, int os) {
775+
char *exe, int fd, long *sp, long *auxv,
776+
unsigned long pagesz, int os) {
812777
long i, rc;
813778
unsigned size;
814779
struct ElfEhdr *e;
@@ -923,7 +888,7 @@ static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf,
923888
}
924889

925890
/* we're now ready to load */
926-
Spawn(os, exe, path, fd, sp, pagesz, e, p);
891+
Spawn(os, exe, fd, sp, pagesz, e, p);
927892
}
928893

929894
__attribute__((__noreturn__)) static void ShowUsage(int os, int fd, int rc) {
@@ -1081,8 +1046,6 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
10811046
Pexit(os, prog, 0, "not found (maybe chmod +x or ./ needed)");
10821047
} else if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) {
10831048
Pexit(os, exe, fd, "open");
1084-
} else if ((rc = RealPath(os, fd, exe, &prog)) < 0) {
1085-
Pexit(os, exe, rc, "realpath");
10861049
} else if ((rc = Pread(fd, ebuf->buf, sizeof(ebuf->buf), 0, os)) < 0) {
10871050
Pexit(os, exe, rc, "read");
10881051
} else if ((unsigned long)rc < sizeof(ebuf->ehdr)) {
@@ -1122,9 +1085,9 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp,
11221085
}
11231086
}
11241087
if (i >= sizeof(ebuf->ehdr)) {
1125-
TryElf(M, ebuf, exe, prog, fd, sp, auxv, pagesz, os);
1088+
TryElf(M, ebuf, exe, fd, sp, auxv, pagesz, os);
11261089
}
11271090
}
11281091
}
1129-
Pexit(os, exe, 0, TryElf(M, ebuf, exe, prog, fd, sp, auxv, pagesz, os));
1092+
Pexit(os, exe, 0, TryElf(M, ebuf, exe, fd, sp, auxv, pagesz, os));
11301093
}

libc/calls/getprogramexecutablename.greg.c

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -63,22 +63,52 @@ static inline int AllNumDot(const char *s) {
6363
}
6464
}
6565

66-
static int IsApeLoader(char *s) {
66+
// old loaders do not pass __program_executable_name, so we need to
67+
// check for them when we use KERN_PROC_PATHNAME et al.
68+
static int OldApeLoader(char *s) {
6769
char *b;
6870
return !strcmp(s, "/usr/bin/ape") ||
6971
(!strncmp((b = basename(s)), ".ape-", 5) &&
7072
AllNumDot(b + 5));
7173
}
7274

75+
// if q exists then turn it into an absolute path. we also try adding
76+
// a .com suffix since the ape auto-appends it when resolving
77+
static int TryPath(const char *q, int com) {
78+
char c, *p, *e;
79+
if (!q) return 0;
80+
p = g_prog.u.buf;
81+
e = p + sizeof(g_prog.u.buf);
82+
if (*q != '/') {
83+
if (q[0] == '.' && q[1] == '/') {
84+
q += 2;
85+
}
86+
int got = __getcwd(p, e - p - 1 /* '/' */ - com * 4);
87+
if (got != -1) {
88+
p += got - 1;
89+
*p++ = '/';
90+
}
91+
}
92+
while ((c = *q++)) {
93+
if (p + com * 4 + 1 /* nul */ < e) {
94+
*p++ = c;
95+
} else {
96+
return 0;
97+
}
98+
}
99+
*p = 0;
100+
if (!sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0)) return 1;
101+
p = WRITE32LE(p, READ32LE(".com"));
102+
*p = 0;
103+
if (!sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0)) return 1;
104+
return 0;
105+
}
106+
73107
static inline void InitProgramExecutableNameImpl(void) {
74108
size_t n;
75109
ssize_t got;
76110
char c, *q, *b;
77111

78-
if (__program_executable_name) {
79-
/* already set by the loader */
80-
return;
81-
}
82112
if (IsWindows()) {
83113
int n = GetModuleFileName(0, g_prog.u.buf16, ARRAYLEN(g_prog.u.buf16));
84114
for (int i = 0; i < n; ++i) {
@@ -103,6 +133,22 @@ static inline void InitProgramExecutableNameImpl(void) {
103133
return;
104134
}
105135

136+
// loader passed us a path. it may be relative.
137+
if (__program_executable_name) {
138+
if (*__program_executable_name == '/') {
139+
return;
140+
}
141+
if (TryPath(__program_executable_name, 0)) {
142+
goto UseBuf;
143+
}
144+
/* if TryPath fails, it probably failed because getcwd() was too long.
145+
we are out of options now; KERN_PROC_PATHNAME et al will return the
146+
name of the loader not the binary, and argv et al will at best have
147+
the same problem. just use the relative path we got from the loader
148+
as-is, and accept that if we chdir then things will break. */
149+
return;
150+
}
151+
106152
b = g_prog.u.buf;
107153
n = sizeof(g_prog.u.buf) - 1;
108154
if (IsFreebsd() || IsNetbsd()) {
@@ -116,7 +162,7 @@ static inline void InitProgramExecutableNameImpl(void) {
116162
}
117163
cmd[3] = -1; // current process
118164
if (sys_sysctl(cmd, ARRAYLEN(cmd), b, &n, 0, 0) != -1) {
119-
if (!IsApeLoader(b)) {
165+
if (!OldApeLoader(b)) {
120166
goto UseBuf;
121167
}
122168
}
@@ -125,54 +171,27 @@ static inline void InitProgramExecutableNameImpl(void) {
125171
if ((got = sys_readlinkat(AT_FDCWD, "/proc/self/exe", b, n)) > 0 ||
126172
(got = sys_readlinkat(AT_FDCWD, "/proc/curproc/file", b, n)) > 0) {
127173
b[got] = 0;
128-
if (!IsApeLoader(b)) {
174+
if (!OldApeLoader(b)) {
129175
goto UseBuf;
130176
}
131177
}
132178
}
133179

180+
// don't trust argument parsing if set-id.
134181
if (issetugid()) {
135-
/* give up prior to using less secure methods */
136182
goto UseEmpty;
137183
}
138184

139-
// if argv[0] exists then turn it into an absolute path. we also try
140-
// adding a .com suffix since the ape auto-appends it when resolving
141-
if ((q = __argv[0])) {
142-
char *p = g_prog.u.buf;
143-
char *e = p + sizeof(g_prog.u.buf);
144-
if (*q != '/') {
145-
if (q[0] == '.' && q[1] == '/') {
146-
q += 2;
147-
}
148-
int got = __getcwd(p, e - p - 1 - 4); // for / and .com
149-
if (got != -1) {
150-
p += got - 1;
151-
*p++ = '/';
152-
}
153-
}
154-
while ((c = *q++)) {
155-
if (p + 1 + 4 < e) { // for nul and .com
156-
*p++ = c;
157-
}
158-
}
159-
*p = 0;
160-
if (!sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0)) goto UseBuf;
161-
p = WRITE32LE(p, READ32LE(".com"));
162-
*p = 0;
163-
if (!sys_faccessat(AT_FDCWD, g_prog.u.buf, F_OK, 0)) goto UseBuf;
164-
}
165-
166-
/* the previous loader supplied the full program path as the first
167-
environment variable. we also try "_". */
168-
if ((q = __getenv(__envp, "COSMOPOLITAN_PROGRAM_EXECUTABLE").s) ||
169-
(q = __getenv(__envp, "_").s)) {
170-
goto CopyString;
185+
// try argv[0], then argv[0].com, then $_, then $_.com.
186+
if (TryPath(__argv[0], 1) ||
187+
/* TODO(mrdomino): remove after next loader mint */
188+
TryPath(__getenv(__envp, "COSMOPOLITAN_PROGRAM_EXECUTABLE").s, 0) ||
189+
TryPath(__getenv(__envp, "_").s, 1)) {
190+
goto UseBuf;
171191
}
172192

173193
// give up and just copy argv[0] into it
174194
if ((q = __argv[0])) {
175-
CopyString:
176195
char *p = g_prog.u.buf;
177196
char *e = p + sizeof(g_prog.u.buf);
178197
while ((c = *q++)) {
@@ -184,8 +203,8 @@ static inline void InitProgramExecutableNameImpl(void) {
184203
goto UseBuf;
185204
}
186205

187-
// if we don't even have that then empty the string
188206
UseEmpty:
207+
// if we don't even have that then empty the string
189208
g_prog.u.buf[0] = 0;
190209

191210
UseBuf:

0 commit comments

Comments
 (0)