summaryrefslogtreecommitdiff
path: root/loader/loader.c
blob: 53a2c38f1dbda080d53b444894d693e8c1f63be7 (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
/*
 * 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

/*
 * 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.
 */
int main(int argc, char *argv[], char *envp[])
{
	char **new_argv = calloc(argc + 6, sizeof(char *));
	char preload[PATH_MAX];
	char target[PATH_MAX];
	ssize_t i, j, len;

	strcpy(preload, LIBGCOMPAT " ");
	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';

	new_argv[0] = LOADER;
	new_argv[1] = "--argv0";
	new_argv[2] = argv[0];
	new_argv[3] = "--preload";
	new_argv[4] = preload;
	new_argv[5] = target;
	for (i = 6, 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;
}