capsule-dlmopen.c 9.88 KB
Newer Older
1
2
3
4
5
6
// Copyright © 2017 Collabora Ltd

// This file is part of libcapsule.

// libcapsule is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
Simon McVittie's avatar
Simon McVittie committed
7
// published by the Free Software Foundation; either version 2.1 of the
8
9
10
11
12
13
14
15
16
17
// License, or (at your option) any later version.

// libcapsule is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.

Simon McVittie's avatar
Simon McVittie committed
18
#include <inttypes.h>
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <string.h>
#include <fcntl.h>

#include <libelf.h>
#include <gelf.h>
#include <dlfcn.h>

37
#include <capsule/capsule.h>
38
#include "capsule/capsule-private.h"
39

40
41
42
43
#include "utils/utils.h"
#include "utils/dump.h"
#include "utils/mmap-info.h"
#include "utils/process-pt-dynamic.h"
44
45
#include "utils/ld-cache.h"
#include "utils/ld-libs.h"
46
47
48
49

// ==========================================================================
// some pretty printers for debugging:

50
// dump out the contents of the ld cache to stderr:
51
static void
52
dump_ld_cache (ld_libs *ldlibs)
53
{
54
    ld_cache_foreach( &ldlibs->ldcache, ld_entry_dump, stderr );
55
56
}

Simon McVittie's avatar
Simon McVittie committed
57
58
59
60
61
62
63
64
65
/*
 * wrap:
 * @name:
 * @base: Starting address of the program header in memory.
 *  Addresses are normally relative to this, except for when they are
 *  absolute (see fix_addr()).
 * @dyn: An array of ElfW(Dyn) structures, somewhere after @base
 * @wrappers: Relocations to apply
 */
66
67
68
69
static void
wrap (const char *name,
      ElfW(Addr) base,
      ElfW(Dyn) *dyn,
70
      capsule_item *wrappers)
71
72
{
    int mmap_errno = 0;
73
    const char *mmap_error = NULL;
74
75
76
    ElfW(Addr) start = (ElfW(Addr)) dyn - base;
    // we don't know the size so we'll have to rely on the linker putting
    // well formed entries into the mmap()ed DSO region.
77
78
    // (tbf if the linker is putting duff entries here we're boned anyway)
    //
79
80
81
82
    // dyn is the address of the dynamic section
    // base is the start of the program header in memory
    // start should be the offset from the program header to its dyn section
    //
83
    // the utility functions expect an upper bound though so set that to
84
    // something suitably large:
85
    relocation_data rdata = { 0 };
86

Simon McVittie's avatar
Simon McVittie committed
87
88
89
90
    DEBUG( DEBUG_WRAPPERS,
           "\"%s\": base address %" PRIxPTR ", dynamic section at %p",
           name, base, dyn );

91
    rdata.debug     = debug_flags;
92
93
    rdata.error     = NULL;
    rdata.relocs    = wrappers;
94
95
96
97

    // if RELRO linking has happened we'll need to tweak the mprotect flags
    // before monkeypatching the symbol tables, for which we will need the
    // sizes, locations and current protections of any mmap()ed regions:
98
99
    rdata.mmap_info = load_mmap_info( &mmap_errno, &mmap_error );

100
    if( mmap_errno || mmap_error )
101
    {
102
103
104
105
        DEBUG( DEBUG_MPROTECT,
               "mmap/mprotect flags information load error (errno: %d): %s",
               mmap_errno, mmap_error );
        DEBUG( DEBUG_MPROTECT,
106
               "relocation will be unable to handle RELRO linked libraries" );
107
108
    }

109
    // make all the mmap()s writable:
110
111
112
113
    for( int i = 0; rdata.mmap_info[i].start != MAP_FAILED; i++ )
        if( mmap_entry_should_be_writable( &rdata.mmap_info[i] ) )
            add_mmap_protection( &rdata.mmap_info[i], PROT_WRITE );

114
115
116
117
    // if we're debugging wrapper installation in detail we
    // will end up in a path that's normally only DEBUG_ELF
    // debugged:
    if( debug_flags & DEBUG_WRAPPERS )
118
        debug_flags = debug_flags | DEBUG_RELOCS;
119

120
    // install any required wrappers inside the capsule:
121
    process_pt_dynamic( start,  // offset from phdr to dyn section
122
                        0,      //  fake size value
123
                        (void *) base,   //  address of phdr in memory
124
125
126
127
                        process_dt_rela,
                        process_dt_rel,
                        &rdata );

128
129
130
    // put the debug flags back in case we changed them
    debug_flags = rdata.debug;

131
    // put the mmap()/mprotect() permissions back the way they were:
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
    for( int i = 0; rdata.mmap_info[i].start != MAP_FAILED; i++ )
        if( mmap_entry_should_be_writable( &rdata.mmap_info[i] ) )
            reset_mmap_protection( &rdata.mmap_info[i] );

    free_mmap_info( rdata.mmap_info );
    rdata.mmap_info = NULL;
}

static inline int
excluded_from_wrap (const char *name, char **exclude)
{
    const char *dso = strrchr(name, '/');

    // we can't ever subvert the runtime linker itself:
    if( strncmp( "/ld-", dso, 4 ) == 0 )
        return 1;

    for( char **x = exclude; x && *x; x++ )
        if( strcmp ( *x, dso + 1 ) == 0 )
            return 1;

    return 0;
}

156
157
158
159
160
/*
 * @exclude: (array zero-terminated=1): same as for capsule_dlmopen()
 * @errcode: (out): errno
 * @error: (out) (transfer full) (optional): Error string
 */
161
162
163
// replace calls out to dlopen in the encapsulated DSO with a wrapper
// which should take care of preserving the /path-prefix and namespace
// wrapping of the original capsule_dlmopen() call.
164
165
166
//
// strictly speaking we can wrap things other than dlopen(),
// but that's currently all we use this for:
167
static int install_wrappers ( void *dl_handle,
168
                              capsule_item *wrappers,
169
170
171
172
173
174
175
176
177
                              const char **exclude,
                              int *errcode,
                              char **error)
{
    int replacements = 0;
    struct link_map *map;

    if( dlinfo( dl_handle, RTLD_DI_LINKMAP, &map ) != 0 )
    {
178
179
        const char *local_error = dlerror();

180
        if( error )
181
            *error = xstrdup( local_error );
182
183
184
185

        if( errcode )
            *errcode = EINVAL;

186
        DEBUG( DEBUG_WRAPPERS, "mangling capsule symbols: %s", local_error );
187
188
189
190

        return -1;
    }

191
    DEBUG( DEBUG_WRAPPERS, "link_map: %p <- %p -> %p",
192
193
194
195
196
               map ? map->l_next : NULL ,
               map ? map         : NULL ,
               map ? map->l_prev : NULL );

    // no guarantee that we're at either end of the link map:
197
198
    while( map->l_prev )
        map = map->l_prev;
199

200
201
202
203
204
    unsigned long df = debug_flags;

    if( debug_flags & DEBUG_WRAPPERS )
        debug_flags |= DEBUG_RELOCS;

205
    if (map->l_next)
Simon McVittie's avatar
Simon McVittie committed
206
    {
207
        for( struct link_map *m = map; m; m = m->l_next )
Simon McVittie's avatar
Simon McVittie committed
208
209
210
211
212
213
214
215
        {
            if( excluded_from_wrap(m->l_name, (char **)exclude) )
            {
                DEBUG( DEBUG_WRAPPERS, "%s excluded from wrapping",
                       m->l_name );
            }
            else
            {
216
                wrap( m->l_name, m->l_addr, m->l_ld, wrappers );
Simon McVittie's avatar
Simon McVittie committed
217
218
219
            }
        }
    }
220

221
222
    debug_flags = df;

223
224
    return replacements;
}
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
// dump the link map info for the given dl handle (NULL = default)
static void
dump_link_map( void *dl_handle )
{
    struct link_map *map;
    void *handle;

    if( !dl_handle )
        handle = dlopen( NULL, RTLD_LAZY|RTLD_NOLOAD );
    else
        handle = dl_handle;

    if( dlinfo( handle, RTLD_DI_LINKMAP, &map ) != 0 )
    {
        DEBUG( DEBUG_CAPSULE, "failed to access link_map for handle %p-%p: %s",
               dl_handle, handle, dlerror() );
        return;
    }

    // be kind, rewind the link map:
    while( map->l_prev )
        map = map->l_prev;

    fprintf( stderr, "(dl-handle %s", dl_handle ? "CAPSULE" : "DEFAULT" );
    for( struct link_map *m = map; m; m = m->l_next )
Simon McVittie's avatar
Simon McVittie committed
251
252
        fprintf( stderr, "\n  [prev: %p] %p: \"%s\" [next: %p]",
                 m->l_prev, m, m->l_name, m->l_next );
253
254
255
    fprintf( stderr, ")\n" );
}

256
257
// ==========================================================================
void *
258
259
260
261
_capsule_load (const capsule cap,
               capsule_item *wrappers,
               int *errcode,
               char **error)
262
263
{
    void *ret = NULL;
264
    ld_libs ldlibs = {};
265

266
    if( !ld_libs_init( &ldlibs,
267
                       (const char **) cap->ns->combined_exclude,
268
                       cap->ns->prefix, debug_flags, errcode, error ) )
269
        return NULL;
270
271
272
273

    // ==================================================================
    // read in the ldo.so.cache - this will contain all architectures
    // currently installed (x86_64, i386, x32) in no particular order
274
    if( ld_libs_load_cache( &ldlibs, "/etc/ld.so.cache", errcode, error ) )
275
    {
276
        if( debug_flags & DEBUG_LDCACHE )
277
278
279
280
281
282
283
284
285
            dump_ld_cache( &ldlibs );
    }
    else
    {
        return NULL;
    }

    // ==================================================================
    // find the starting point of our capsule
286
    if( !ld_libs_set_target( &ldlibs, cap->meta->soname, errcode, error ) )
287
288
289
        goto cleanup;

    // ==================================================================
290
    // once we have the starting point recursively find all its DT_NEEDED
291
292
    // entries, except for the linker itself and libc, which must not
    // be different between the capsule and the "real" DSO environment:
293
    if( !ld_libs_find_dependencies( &ldlibs, errcode, error ) )
294
295
296
        goto cleanup;

    // ==================================================================
297
    // load the stack of DSOs we need:
298
    ret = ld_libs_load( &ldlibs, &cap->ns->ns, 0, errcode, error );
299

300
301
302
303
304
    if( debug_flags & DEBUG_CAPSULE )
    {
        dump_link_map( ret  );
        dump_link_map( NULL );
    }
305

306
307
    if( !ret )
        goto cleanup;
308
309

    // TODO: failure in the dlopen fixup phase should probably be fatal:
310
311
    if( ret      != NULL && // no errors so far
        wrappers != NULL )  // have a dlopen fixup function
312
        install_wrappers( ret, wrappers,
313
                          (const char **)cap->ns->combined_exclude,
314
                          errcode, error );
315
316

    cap->dl_handle = ret;
317
318

cleanup:
319
    ld_libs_finish( &ldlibs );
320
321
322
    return ret;
}