capsule-relocate.c 7.53 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
18
19
20
21
22
23
24
25
26
27
28
// 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/>.

#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>

#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <capsule/capsule.h>
29
#include "capsule/capsule-private.h"
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

#include "utils/dump.h"
#include "utils/utils.h"
#include "utils/process-pt-dynamic.h"

// ==========================================================================
// some entries require us to peer into others to make sense of them:
// can't make full sense of relocations without looking names up
// in the stringtab, which does not have to occur at any fixed point in
// in the PT_DYNAMIC entry.
// IOW PT_DYNAMIC contains both relocations (DT_RELA, DT_REL) and a stringtab
// (DT_STRTAB) in arbitrary order but the former do not make sense without
// the latter.


// =========================================================================
Simon McVittie's avatar
Simon McVittie committed
46
47
48
49
50
51
52
53
54

/*
 * process_phdr:
 * @info: struct dl_phdr_info describing the shared object
 * @size: sizeof(info)
 * @rdata: the closure that we passed to dl_iterate_phdr()
 *
 * Callback called for each shared object loaded into the program.
 */
55
56
57
static int
process_phdr (struct dl_phdr_info *info,
              size_t size,
58
              relocation_data *rdata)
59
60
61
62
{
    int ret = 0;

    for( int j = 0; !ret && (j < info->dlpi_phnum); j++ )
63
    {
64
        if( info->dlpi_phdr[j].p_type == PT_DYNAMIC )
65
        {
66
            ret = process_pt_dynamic( info->dlpi_phdr[j].p_vaddr,
67
                                      info->dlpi_phdr[j].p_memsz,
68
                                      (void *) info->dlpi_addr,
69
70
71
                                      process_dt_rela,
                                      process_dt_rel,
                                      rdata );
72
73
        }
    }
74

75
    if( ret == 0 && rdata->seen != NULL )
76
        ptr_list_push_addr( rdata->seen, info->dlpi_addr );
77

78
79
80
    return ret;
}

81
static int
82
dso_is_blacklisted (const char *path, relocation_flags flags)
83
{
84
    const char * const *soname;
85
86
87
88
89
90
91
    const char * const libc[] =
    {
        "libc.so",
        "libdl.so",
        "libpthread.so",
        NULL
    };
92
93
94
95
    static const char *never = "libcapsule.so";

    if( soname_matches_path( never, path ) )
        return 1;
96

97
98
99
100
    if( flags & RELOCATION_FLAGS_AVOID_LIBC )
        for( soname = libc; soname && *soname; soname++ )
            if( soname_matches_path( *soname, path ) )
                return 1;
101
102
103
104

    return 0;
}

105
106
107
108
109
110
111
112
113
114
115
116
static int
dso_has_been_relocated (ptr_list *seen, ElfW(Addr) base)
{
    if( seen == NULL )
        return 0;

    if( ptr_list_contains( seen, base ) )
        return 1;

    return 0;
}

117
118
119
120
121
122
123
124
// first level of the callback: all we're doing here is skipping over
// any program headers that (for whatever reason) we decide we're not
// interested in.
// In practice we have to handle all existing DSOs, as any of them may
// call into the library we are acting as a shim for.
static int
relocate_cb (struct dl_phdr_info *info, size_t size, void *data)
{
125
    relocation_data *rdata = data;
126
    const char *dso_path = *info->dlpi_name ? info->dlpi_name : "-elf-";
127

128
    if( dso_is_blacklisted( dso_path, rdata->flags ) )
129
    {
130
131
132
133
134
135
136
137
138
        DEBUG( DEBUG_RELOCS, "skipping %s %p (blacklisted)",
               dso_path, (void *) info->dlpi_addr );
        return 0;
    }

    if( dso_has_been_relocated( rdata->seen, info->dlpi_addr ) )
    {
        DEBUG( DEBUG_RELOCS, "skipping %s %p (already relocated)",
               dso_path, (void *) info->dlpi_addr );
139
        return 0;
140
141
    }

142
143
    DEBUG( DEBUG_RELOCS, "processing %s %p",
           dso_path, (void *) info->dlpi_addr );
144
145
146
147

    return process_phdr( info, size, rdata );
}

148
149
static int relocate (const capsule cap,
                     capsule_item *relocations,
150
                     relocation_flags flags,
151
                     ptr_list *seen,
152
                     char **error)
153
{
154
    relocation_data rdata = { 0 };
155
    capsule_item *map;
156
    int mmap_errno = 0;
157
    const char *mmap_error = NULL;
158
159
    int rval = 0;

160
    // load the relevant metadata into the callback argument:
161
    rdata.debug     = debug_flags;
162
    rdata.error     = NULL;
163
    rdata.flags     = flags;
164
    rdata.mmap_info = load_mmap_info( &mmap_errno, &mmap_error );
165
    rdata.relocs    = relocations;
166
    rdata.seen      = seen;
167

168
    if( mmap_errno || mmap_error )
169
    {
170
171
172
173
174
        DEBUG( DEBUG_RELOCS|DEBUG_MPROTECT,
               "mmap/mprotect flags information load error (errno: %d): %s\n",
               mmap_errno, mmap_error );
        DEBUG( DEBUG_RELOCS|DEBUG_MPROTECT,
               "relocation will be unable to handle relro linked libraries" );
175
176
177
178
    }

    // no source dl handle means we must have a pre-populated
    // map of shim-to-real function pointers in `relocations',
179
    // otherwise populate the map using [the real] dlsym():
180
    if( cap->dl_handle )
181
        for( map = cap->meta->items; map->name; map++ )
182
183
        {
            if( !map->shim )
184
                map->shim = (ElfW(Addr)) _capsule_original_dlsym( RTLD_DEFAULT, map->name );
185
186

            if( !map->real )
187
                map->real = (ElfW(Addr)) _capsule_original_dlsym( cap->dl_handle, map->name );
188
189
190
191
192
        }

    // time to enter some sort of ... dangerous... zone:
    // we need the mmap()ed DSO regions to have the PROT_WRITE
    // flag set, so that if they've been RELRO linked we can still
193
    // overwrite their GOT entries.
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
    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 );

    dl_iterate_phdr( relocate_cb, &rdata );

    // and now we put those PROT_WRITE permissions back the way they were:
    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] );

    if( rdata.error )
    {
        if( error )
            *error = rdata.error;
        else
            free( rdata.error );

        rval = (rdata.count.failure == 0) ? -1 : rdata.count.failure;
    }

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

    return rval;
}
220
221

int
222
_capsule_relocate (const capsule cap, char **error)
223
{
224
    DEBUG( DEBUG_RELOCS, "beginning global symbol relocation:" );
225
    return relocate( cap, cap->meta->items, RELOCATION_FLAGS_NONE, cap->seen.all, error );
226
227
}

228
229
230
231
232
233
234
235
static capsule_item capsule_external_dl_relocs[] =
{
  { "dlopen",
    (capsule_addr) capsule_external_dlopen ,
    (capsule_addr) capsule_external_dlopen },
  { NULL }
};

236
int
237
238
_capsule_relocate_dlopen (const capsule cap,
                          char **error)
239
{
240
241
    unsigned long df = debug_flags;

242
243
244
    if( debug_flags & DEBUG_DLFUNC )
        debug_flags |= DEBUG_RELOCS;

245
    DEBUG( DEBUG_RELOCS, "beginning restricted symbol relocation:" );
246
247
    int rv = relocate( cap, capsule_external_dl_relocs,
                       RELOCATION_FLAGS_AVOID_LIBC, cap->seen.some, error );
248
249
250
251

    debug_flags = df;

    return rv;
252
}