summaryrefslogtreecommitdiff
path: root/loader/loader.c
blob: ba4b4b70ca7a914beeb83a1c31ef43a5feafa7c0 (plain) (blame)
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
/*
 * Copyright (c) 2017 William Pitcock <nenolod@dereferenced.org>
 * Copyright (c) 2018 Samuel Holland <samuel@sholland.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * This software is provided 'as is' and without any warranty, express or
 * implied.  In no event shall the authors be liable for any damages arising
 * from the use of this software.
 */

#include <limits.h> /* PATH_MAX */
#include <stddef.h> /* NULL */
#include <stdio.h>  /* fputs, fwrite, stderr */
#include <stdlib.h> /* calloc, EXIT_FAILURE, getenv */
#include <string.h> /* strcpy, strlcpy */
#include <unistd.h> /* execve, ssize_t */

#ifndef PATH_MAX
#define PATH_MAX 16384
#endif

#ifndef LIBGCOMPAT
#error LIBGCOMPAT must be defined
#endif

#ifndef LINKER
#error LINKER must be defined
#endif

#ifndef LOADER
#error LOADER must be defined
#endif

void usage(void)
{
	printf("This is the gcompat ELF interpreter stub.\n");
	printf("You are not meant to run this directly.\n");
}

/*
 * Given the argv { "foo", "bar", "baz" }, and the environment variable
 * "LD_PRELOAD=/another/preload.so", the new argv will be {
 *         "ld-linux-$ARCH.so.N",
 *         "--argv0",
 *         "foo",
 *         "--preload",
 *         "/path/to/libgcompat.so /another/preload.so",
 *         "--",
 *         "/path/to/foo",
 *         "bar",
 *         "baz"
 * }.
 *
 * NOTE: The new argv needs 6 more entries than the original argc: five for
 * arguments added at the beginning, and one for the NULL sentinel at the end
 * not included in argc. Because calloc() is used, the sentinel is not
 * explicitly set on its own, but *it is still there*!
 *
 * NOTE: The name given in argv[0] to musl *must* match glibc's ld.so filename.
 * Many glibc-linked programs have an explicit dependency on its ld.so, but the
 * file placed at that path (this loader) is not a real shared library. If musl
 * tries to load this file as a shared library, it will fail. Thankfully, since
 * musl treats argv[0] as "its" name when run directly, it considers a library
 * by that name to already be loaded, and ignores the problematic dependency.
 *
 * NOTE: LD_PRELOAD entries are processed in order, and the environment variable
 * is ignored if the parameter is given. In case the glibc-linked program needs
 * another preloaded library, it should be appended to the argument so it gets
 * loaded after libgcompat. Leave the environment variable as is, so if the
 * program runs other glibc-linked programs, the same transformation gets
 * applied there as well.
 *
 * NOTE: In order for the program to be able to re-exec itself it needs the
 * fully-resolved path from /proc/self/exe ("target" below). This path is given
 * to musl on the command line in new_argv[5], but it is not directly available
 * to the program. An intercepted readlink("/proc/self/exe") must read it from
 * /proc/self/cmdline. Thus, if its position in new_argv changes, that function
 * must also be updated to match.
 *
 * NOTE: We have to use 7 arguments to ensure alignment is correct on AArch64,
 * so we add an additional --, leaving the 8th argument as the binary name.
 */
int main(int argc, char *argv[], char *envp[])
{
	char **new_argv = calloc(argc + 7, sizeof(char *));
	char preload[PATH_MAX] = "";
	char target[PATH_MAX] = "";
	ssize_t i, j, len;

	strlcpy(preload, LIBGCOMPAT " ", sizeof(preload));
	if (getenv("LD_PRELOAD") != NULL) {
		len = strlcat(preload, getenv("LD_PRELOAD"), sizeof(preload));
		if ((size_t) len >= sizeof(preload)) {
			fputs("too many preloaded libraries", stderr);
			return EXIT_FAILURE;
		}
	}

	len = readlink("/proc/self/exe", target, sizeof(target));
	if (len < 0 || len == sizeof(target)) {
		perror("readlink");
		return EXIT_FAILURE;
	}
	target[len] = '\0';

	/* somebody is trying to run the loader directly */
	if (strstr(target, LOADER) != NULL) {
		usage();
		return EXIT_FAILURE;
	}

	new_argv[0] = LOADER;
	new_argv[1] = "--argv0";
	new_argv[2] = argv[0];
	new_argv[3] = "--preload";
	new_argv[4] = preload;
	new_argv[5] = "--";
	new_argv[6] = target;
	for (i = 7, j = 1; j < argc; ++i, ++j) {
		new_argv[i] = argv[j];
	}

	execve(LINKER, new_argv, envp);

	/* execve only returns if there is an error. */
	perror("execve");
	return EXIT_FAILURE;
}