summaryrefslogtreecommitdiff
path: root/test/dlwrap.c
blob: c0c24c21a497a6a57309dfafad05728845bfd782 (plain)
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
/* Copyright © 2013, Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/** @file dlwrap.c
 *
 * Implements a wrapper for dlopen() and dlsym() so that epoxy will
 * end up finding symbols from the testcases named
 * "override_EGL_eglWhatever()" or "override_GLES2_glWhatever()" or
 * "override_GL_glWhatever()" when it tries to dlopen() and dlsym()
 * the real GL or EGL functions in question.
 *
 * This lets us simulate some target systems in the test suite, or
 * just stub out GL functions so we can be sure of what's being
 * called.
 */

/* dladdr is a glibc extension */
#define _GNU_SOURCE
#include <dlfcn.h>

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "dlwrap.h"

#define STRNCMP_LITERAL(var, literal) \
    strncmp ((var), (literal), sizeof (literal) - 1)

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))

void *libfips_handle;

typedef void *(*fips_dlopen_t)(const char *filename, int flag);
typedef void *(*fips_dlsym_t)(void *handle, const char *symbol);

void *override_EGL_eglGetProcAddress(const char *name);
void *override_GL_glXGetProcAddress(const char *name);
void *override_GL_glXGetProcAddressARB(const char *name);
void __dlclose(void *handle);

static struct libwrap {
    const char *filename;
    const char *symbol_prefix;
    void *handle;
} wrapped_libs[] = {
    { "libGL.so", "GL", NULL },
    { "libEGL.so", "EGL", NULL },
    { "libGLESv2.so", "GLES2", NULL },
    { "libOpenGL.so", "GL", NULL},
};

/* Match 'filename' against an internal list of libraries for which
 * libfips has wrappers.
 *
 * Returns true and sets *index_ret if a match is found.
 * Returns false if no match is found. */
static struct libwrap *
find_wrapped_library(const char *filename)
{
    unsigned i;

    if (!filename)
        return NULL;

    for (i = 0; i < ARRAY_SIZE(wrapped_libs); i++) {
        if (strncmp(wrapped_libs[i].filename, filename,
                    strlen(wrapped_libs[i].filename)) == 0) {
            return &wrapped_libs[i];
        }
    }

    return NULL;
}

/* Many (most?) OpenGL programs dlopen libGL.so.1 rather than linking
 * against it directly, which means they would not be seeing our
 * wrapped GL symbols via LD_PRELOAD. So we catch the dlopen in a
 * wrapper here and redirect it to our library.
 */
void *
dlopen(const char *filename, int flag)
{
    void *ret;
    struct libwrap *wrap;

    /* Before deciding whether to redirect this dlopen to our own
     * library, we call the real dlopen. This assures that any
     * expected side-effects from loading the intended library are
     * resolved. Below, we may still return a handle pointing to
     * our own library, and not what is opened here. */
    ret = dlwrap_real_dlopen(filename, flag);

    /* If filename is not a wrapped library, just return real dlopen */
    wrap = find_wrapped_library(filename);
    if (!wrap)
        return ret;

    wrap->handle = ret;

    /* We use wrapped_libs as our handles to libraries. */
    return wrap;
}

/**
 * Wraps dlclose to hide our faked handles from it.
 */
void
__dlclose(void *handle)
{
    struct libwrap *wrap = handle;

    if (wrap < wrapped_libs ||
        wrap >= wrapped_libs + ARRAY_SIZE(wrapped_libs)) {
        void (*real_dlclose)(void *handle) = dlwrap_real_dlsym(RTLD_NEXT, "__dlclose");
        real_dlclose(handle);
    }
}

void *
dlwrap_real_dlopen(const char *filename, int flag)
{
    static fips_dlopen_t real_dlopen = NULL;

    if (!real_dlopen) {
        real_dlopen = (fips_dlopen_t) dlwrap_real_dlsym(RTLD_NEXT, "dlopen");
        if (!real_dlopen) {
            fputs("Error: Failed to find symbol for dlopen.\n", stderr);
            exit(1);
        }
    }

    return real_dlopen(filename, flag);
}

/**
 * Return the dlsym() on the application's namespace for
 * "override_<prefix>_<name>"
 */
static void *
wrapped_dlsym(const char *prefix, const char *name)
{
    char *wrap_name;
    void *symbol;

    if (asprintf(&wrap_name, "override_%s_%s", prefix, name) < 0) {
        fputs("Error: Failed to allocate memory.\n", stderr);
        abort();
    }

    symbol = dlwrap_real_dlsym(RTLD_DEFAULT, wrap_name);
    free(wrap_name);
    return symbol;
}

/* Since we redirect dlopens of libGL.so and libEGL.so to libfips we
 * need to ensure that dlysm succeeds for all functions that might be
 * defined in the real, underlying libGL library. But we're far too
 * lazy to implement wrappers for function that would simply
 * pass-through, so instead we also wrap dlysm and arrange for it to
 * pass things through with RTLD_next if libfips does not have the
 * function desired.  */
void *
dlsym(void *handle, const char *name)
{
    struct libwrap *wrap = handle;

    /* Make sure that handle is actually one of our wrapped libs. */
    if (wrap < wrapped_libs ||
        wrap >= wrapped_libs + ARRAY_SIZE(wrapped_libs)) {
        wrap = NULL;
    }

    /* Failing that, anything specifically requested from the
     * libfips library should be redirected to a real GL
     * library. */

    if (wrap) {
        void *symbol = wrapped_dlsym(wrap->symbol_prefix, name);
        if (symbol)
            return symbol;
        else
            return dlwrap_real_dlsym(wrap->handle, name);
    }

    /* And anything else is some unrelated dlsym. Just pass it
     * through.  (This also covers the cases of lookups with
     * special handles such as RTLD_DEFAULT or RTLD_NEXT.)
     */
    return dlwrap_real_dlsym(handle, name);
}

void *
dlwrap_real_dlsym(void *handle, const char *name)
{
    static fips_dlsym_t real_dlsym = NULL;

    if (!real_dlsym) {
        /* FIXME: This brute-force, hard-coded searching for a versioned
         * symbol is really ugly. The only reason I'm doing this is because
         * I need some way to lookup the "dlsym" function in libdl, but
         * I can't use 'dlsym' to do it. So dlvsym works, but forces me
         * to guess what the right version is.
         *
         * Potential fixes here:
         *
         *   1. Use libelf to actually inspect libdl.so and
         *      find the right version, (finding the right
         *      libdl.so can be made easier with
         *      dl_iterate_phdr).
         *
         *   2. Use libelf to find the offset of the 'dlsym'
         *      symbol within libdl.so, (and then add this to
         *      the base address at which libdl.so is loaded
         *      as reported by dl_iterate_phdr).
         *
         * In the meantime, I'll just keep augmenting this
         * hard-coded version list as people report bugs. */
        const char *version[] = {
            "GLIBC_2.17",
            "GLIBC_2.4",
            "GLIBC_2.3",
            "GLIBC_2.2.5",
            "GLIBC_2.2",
            "GLIBC_2.0",
            "FBSD_1.0"
        };
        int num_versions = sizeof(version) / sizeof(version[0]);
        int i;
        for (i = 0; i < num_versions; i++) {
            real_dlsym = (fips_dlsym_t) dlvsym(RTLD_NEXT, "dlsym", version[i]);
            if (real_dlsym)
                break;
        }
        if (i == num_versions) {
            fputs("Internal error: Failed to find real dlsym\n", stderr);
            fputs("This may be a simple matter of fips not knowing about the version of GLIBC that\n"
                  "your program is using. Current known versions are:\n\n\t",
                  stderr);
            for (i = 0; i < num_versions; i++)
                fprintf(stderr, "%s ", version[i]);
            fputs("\n\nYou can inspect your version by first finding libdl.so.2:\n"
                  "\n"
                  "\tldd <your-program> | grep libdl.so\n"
                  "\n"
                  "And then inspecting the version attached to the dlsym symbol:\n"
                  "\n"
                  "\treadelf -s /path/to/libdl.so.2 | grep dlsym\n"
                  "\n"
                  "And finally, adding the version to dlwrap.c:dlwrap_real_dlsym.\n",
                  stderr);

            exit(1);
        }
    }

    return real_dlsym(handle, name);
}

void *
override_GL_glXGetProcAddress(const char *name)
{
    void *symbol;

    symbol = wrapped_dlsym("GL", name);
    if (symbol)
        return symbol;

    return DEFER_TO_GL("libGL.so.1", override_GL_glXGetProcAddress,
                       "glXGetProcAddress", (name));
}

void *
override_GL_glXGetProcAddressARB(const char *name)
{
    void *symbol;

    symbol = wrapped_dlsym("GL", name);
    if (symbol)
        return symbol;

    return DEFER_TO_GL("libGL.so.1", override_GL_glXGetProcAddressARB,
                       "glXGetProcAddressARB", (name));
}

void *
override_EGL_eglGetProcAddress(const char *name)
{
    void *symbol;

    if (!STRNCMP_LITERAL(name, "gl")) {
        symbol = wrapped_dlsym("GLES2", name);
        if (symbol)
            return symbol;
    }

    if (!STRNCMP_LITERAL(name, "egl")) {
        symbol = wrapped_dlsym("EGL", name);
        if (symbol)
            return symbol;
    }

    return DEFER_TO_GL("libEGL.so.1", override_EGL_eglGetProcAddress,
                       "eglGetProcAddress", (name));
}