forked from gcc-mirror/gcc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvtv_rts.cc
1827 lines (1565 loc) · 65.9 KB
/
vtv_rts.cc
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
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright (C) 2012-2022 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC 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 General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
/* This file is part of the vtable security feature implementation.
The vtable security feature is designed to detect when a virtual
call is about to be made through an invalid vtable pointer
(possibly due to data corruption or malicious attacks). The
compiler finds every virtual call, and inserts a verification call
before the virtual call. The verification call takes the actual
vtable pointer value in the object through which the virtual call
is being made, and compares the vtable pointer against a set of all
valid vtable pointers that the object could contain (this set is
based on the declared type of the object). If the pointer is in
the valid set, execution is allowed to continue; otherwise the
program is halted.
There are several pieces needed in order to make this work: 1. For
every virtual class in the program (i.e. a class that contains
virtual methods), we need to build the set of all possible valid
vtables that an object of that class could point to. This includes
vtables for any class(es) that inherit from the class under
consideration. 2. For every such data set we build up, we need a
way to find and reference the data set. This is complicated by the
fact that the real vtable addresses are not known until runtime,
when the program is loaded into memory, but we need to reference the
sets at compile time when we are inserting verification calls into
the program. 3. We need to find every virtual call in the program,
and insert the verification call (with the appropriate arguments)
before the virtual call. 4. We need some runtime library pieces:
the code to build up the data sets at runtime; the code to actually
perform the verification using the data sets; and some code to set
protections on the data sets, so they themselves do not become
hacker targets.
To find and reference the set of valid vtable pointers for any given
virtual class, we create a special global varible for each virtual
class. We refer to this as the "vtable map variable" for that
class. The vtable map variable has the type "void *", and is
initialized by the compiler to NULL. At runtime when the set of
valid vtable pointers for a virtual class, e.g. class Foo, is built,
the vtable map variable for class Foo is made to point to the set.
During compile time, when the compiler is inserting verification
calls into the program, it passes the vtable map variable for the
appropriate class to the verification call, so that at runtime the
verification call can find the appropriate data set.
The actual set of valid vtable pointers for a polymorphic class,
e.g. class Foo, cannot be built until runtime, when the vtables get
loaded into memory and their addresses are known. But the knowledge
about which vtables belong in which class' hierarchy is only known
at compile time. Therefore at compile time we collect class
hierarchy and vtable information about every virtual class, and we
generate calls to build up the data sets at runtime. To build the
data sets, we call one of the functions we add to the runtime
library, __VLTRegisterPair. __VLTRegisterPair takes two arguments,
a vtable map variable and the address of a vtable. If the vtable
map variable is currently NULL, it creates a new data set (hash
table), makes the vtable map variable point to the new data set, and
inserts the vtable address into the data set. If the vtable map
variable is not NULL, it just inserts the vtable address into the
data set. In order to make sure that our data sets are built before
any verification calls happen, we create a special constructor
initialization function for each compilation unit, give it a very
high initialization priority, and insert all of our calls to
__VLTRegisterPair into our special constructor initialization
function. */
/* This file contains the main externally visible runtime library
functions for vtable verification: __VLTChangePermission,
__VLTRegisterPair, and __VLTVerifyVtablePointer. It also contains
debug versions __VLTRegisterPairDebug and
__VLTVerifyVtablePointerDebug, which have extra parameters in order
to make it easier to debug verification failures.
The final piece of functionality implemented in this file is symbol
resolution for multiple instances of the same vtable map variable.
If the same virtual class is used in two different compilation
units, then each compilation unit will create a vtable map variable
for the class. We need all instances of the same vtable map
variable to point to the same (single) set of valid vtable
pointers for the class, so we wrote our own hashtable-based symbol
resolution for vtable map variables (with a tiny optimization in
the case where there is only one instance of the variable).
There are two other important pieces to the runtime for vtable
verification besides the main pieces that go into libstdc++.so: two
special tiny shared libraries, libvtv_init.so and libvtv_stubs.so.
libvtv_init.so is built from vtv_init.cc. It is designed to help
minimize the calls made to mprotect (see the comments in
vtv_init.cc for more details). Anything compiled with
"-fvtable-verify=std" must be linked with libvtv_init.so (the gcc
driver has been modified to do this). vtv_stubs.so is built from
vtv_stubs.cc. It replaces the main runtime functions
(__VLTChangePermissino, __VLTRegisterPair and
__VLTVerifyVtablePointer) with stub functions that do nothing. If
a programmer has a library that was built with verification, but
wishes to not have verification turned on, the programmer can link
in the vtv_stubs.so library. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#if defined (__CYGWIN__) || defined (__MINGW32__)
#include <windows.h>
#include <winternl.h>
#include <psapi.h>
#else
#include <execinfo.h>
#endif
#include <unistd.h>
#if !defined (__CYGWIN__) && !defined (__MINGW32__)
#include <sys/mman.h>
#include <link.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
/* For gthreads suppport */
#include <bits/c++config.h>
#include <ext/concurrence.h>
#include "vtv_utils.h"
#include "vtv_malloc.h"
#include "vtv_set.h"
#include "vtv_map.h"
#include "vtv_rts.h"
#include "vtv_fail.h"
#include "vtv-change-permission.h"
#ifdef HAVE_GETEXECNAME
const char *program_invocation_name;
#endif
#ifdef HAVE___FORTIFY_FAIL
extern "C" {
/* __fortify_fail is a function in glibc that calls __libc_message,
causing it to print out a program termination error message
(including the name of the binary being terminated), a stack
trace where the error occurred, and a memory map dump. Ideally
we would have called __libc_message directly, but that function
does not appear to be accessible to functions outside glibc,
whereas __fortify_fail is. We call __fortify_fail from
__vtv_really_fail. We looked at calling __libc_fatal, which is
externally accessible, but it does not do the back trace and
memory dump. */
extern void __fortify_fail (const char *) __attribute__((noreturn));
} /* extern "C" */
#else
#if defined (__CYGWIN__) || defined (__MINGW32__)
// porting: fix link error to libc
void __fortify_fail (const char * msg){
OutputDebugString(msg);
abort();
}
#else
// FIXME: Provide backtrace via libbacktrace?
void __fortify_fail (const char *msg) {
write (2, msg, strlen (msg));
abort ();
}
#endif
#endif
/* The following variables are used only for debugging and performance
tuning purposes. Therefore they do not need to be "protected".
They cannot be used to attack the vtable verification system and if
they become corrupted it will not affect the correctness or
security of any of the rest of the vtable verification feature. */
unsigned int num_calls_to_regset = 0;
unsigned int num_calls_to_regpair = 0;
unsigned int num_calls_to_verify_vtable = 0;
unsigned long long regset_cycles = 0;
unsigned long long regpair_cycles = 0;
unsigned long long verify_vtable_cycles = 0;
/* Be careful about initialization of statics in this file. Some of
the routines below are called before any runtime initialization for
statics in this file will be done. For example, dont try to
initialize any of these statics with a runtime call (for ex:
sysconf). The initialization will happen after calls to the routines
to protect/unprotec the vtabla_map variables */
/* No need to mark the following variables with VTV_PROTECTED_VAR.
These are either const or are only used for debugging/tracing.
debugging/tracing will not be ON on production environments */
static const bool debug_hash = HASHTABLE_STATS;
#ifdef VTV_DEBUG
static const int debug_functions = 1;
static const int debug_init = 1;
static const int debug_verify_vtable = 1;
#else
static const int debug_functions = 0;
static const int debug_init = 0;
static const int debug_verify_vtable = 0;
#endif
/* Global file descriptor variables for logging, tracing and debugging. */
static int init_log_fd = -1;
static int verify_vtable_log_fd = -1;
/* This holds a formatted error logging message, to be written to the
vtable verify failures log. */
static char debug_log_message[1024];
#ifdef __GTHREAD_MUTEX_INIT
static __gthread_mutex_t change_permissions_lock = __GTHREAD_MUTEX_INIT;
#else
static __gthread_mutex_t change_permissions_lock;
#endif
#ifndef VTV_STATS
#define VTV_STATS 0
#endif
#if VTV_STATS
static inline unsigned long long
get_cycle_count (void)
{
return rdtsc();
}
static inline void
accumulate_cycle_count (unsigned long long *sum, unsigned long long start)
{
unsigned long long end = rdtsc();
*sum = *sum + (end - start);
}
static inline void
increment_num_calls (unsigned int *num_calls)
{
*num_calls = *num_calls + 1;
}
#else
static inline unsigned long long
get_cycle_count (void)
{
return (unsigned long long) 0;
}
static inline void
accumulate_cycle_count (unsigned long long *sum __attribute__((__unused__)),
unsigned long long start __attribute__((__unused__)))
{
/* Do nothing. */
}
static inline void
increment_num_calls (unsigned int *num_calls __attribute__((__unused__)))
{
/* Do nothing. */
}
#endif
/* Types needed by insert_only_hash_sets. */
typedef uintptr_t int_vptr;
/* The set of valid vtable pointers for each virtual class is stored
in a hash table. This is the hashing function used for the hash
table. For more information on the implementation of the hash
table, see the class insert_only_hash_sets in vtv_set.h. */
struct vptr_hash
{
/* Hash function, used to convert vtable pointer, V, (a memory
address) into an index into the hash table. */
size_t
operator() (int_vptr v) const
{
const uint32_t x = 0x7a35e4d9;
const int shift = (sizeof (v) == 8) ? 23 : 21;
v = x * v;
return v ^ (v >> shift);
}
};
/* This is the memory allocator used to create the hash table data
sets of valid vtable pointers. We use VTV_malloc in order to keep
track of which pages have been allocated, so we can update the
protections on those pages appropriately. See the class
insert_only_hash_sets in vtv_set.h for more information. */
struct vptr_set_alloc
{
/* Memory allocator operator. N is the number of bytes to be
allocated. */
void *
operator() (size_t n) const
{
return __vtv_malloc (n);
}
};
/* Instantiate the template classes (in vtv_set.h) for our particular
hash table needs. */
typedef insert_only_hash_sets<int_vptr, vptr_hash, vptr_set_alloc> vtv_sets;
typedef vtv_sets::insert_only_hash_set vtv_set;
typedef vtv_set * vtv_set_handle;
typedef vtv_set_handle * vtv_set_handle_handle;
/* Records for caching the section header information that we have
read out of the file(s) on disk (in dl_iterate_phdr_callback), to
avoid having to re-open and re-read the same file multiple
times. */
struct sect_hdr_data
{
#if defined (__CYGWIN__) || defined (__MINGW32__)
uintptr_t dlpi_addr; /* The header address in the INFO record,
passed in from dl_iterate_phdr. */
uintptr_t mp_low; /* Start address of the .vtable_map_vars
section in memory. */
#else
ElfW (Addr) dlpi_addr; /* The header address in the INFO record,
passed in from dl_iterate_phdr. */
ElfW (Addr) mp_low; /* Start address of the .vtable_map_vars
section in memory. */
#endif
size_t mp_size; /* Size of the .vtable_map_vars section in
memory. */
};
/* Array for caching the section header information, read from file,
to avoid re-opening and re-reading the same file over-and-over
again. */
#define MAX_ENTRIES 250
static struct sect_hdr_data vtv_sect_info_cache[MAX_ENTRIES] VTV_PROTECTED_VAR;
unsigned int num_cache_entries VTV_PROTECTED_VAR = 0;
/* This function takes the LOAD_ADDR for an object opened by the
dynamic loader, and checks the array of cached file data to see if
there is an entry with the same addres. If it finds such an entry,
it returns the record for that entry; otherwise it returns
NULL. */
#if defined (__CYGWIN__) || defined (__MINGW32__)
struct sect_hdr_data *
search_cached_file_data (uintptr_t load_addr)
#else
struct sect_hdr_data *
search_cached_file_data (ElfW (Addr) load_addr)
#endif
{
unsigned int i;
for (i = 0; i < num_cache_entries; ++i)
{
if (vtv_sect_info_cache[i].dlpi_addr == load_addr)
return &(vtv_sect_info_cache[i]);
}
return NULL;
}
/* This function tries to read COUNT bytes out of the file referred to
by FD into the buffer BUF. It returns the actual number of bytes
it succeeded in reading. */
static size_t
ReadPersistent (int fd, void *buf, size_t count)
{
char *buf0 = (char *) buf;
size_t num_bytes = 0;
while (num_bytes < count)
{
int len;
len = read (fd, buf0 + num_bytes, count - num_bytes);
if (len < 0)
return -1;
if (len == 0)
break;
num_bytes += len;
}
return num_bytes;
}
/* This function tries to read COUNT bytes, starting at OFFSET from
the file referred to by FD, and put them into BUF. It calls
ReadPersistent to help it do so. It returns the actual number of
bytes read, or -1 if it fails altogether. */
static size_t
ReadFromOffset (int fd, void *buf, const size_t count, const off_t offset)
{
off_t off = lseek (fd, offset, SEEK_SET);
if (off != (off_t) -1)
return ReadPersistent (fd, buf, count);
return -1;
}
/* The function takes a MESSAGE and attempts to write it to the vtable
memory protection log (for debugging purposes). If the file is not
open, it attempts to open the file first. */
static void
log_memory_protection_data (char *message)
{
static int log_fd = -1;
if (log_fd == -1)
log_fd = __vtv_open_log ("vtv_memory_protection_data.log");
__vtv_add_to_log (log_fd, "%s", message);
}
#if defined (__CYGWIN__) || defined (__MINGW32__)
static void
read_section_offset_and_length (char *name,
uintptr_t addr,
const char *sect_name,
int mprotect_flags,
off_t *sect_offset,
WORD *sect_len)
{
bool found = false;
struct sect_hdr_data *cached_data = NULL;
/* Check to see if we already have the data for this file. */
cached_data = search_cached_file_data (addr);
if (cached_data)
{
*sect_offset = cached_data->mp_low;
*sect_len = cached_data->mp_size;
return;
}
// check for DOS Header magic bytes
if (*(WORD *)addr == 0x5A4D)
{
int name_len = strlen (sect_name);
int fd = -1;
/* Attempt to open the binary file on disk. */
if (strlen (name) == 0)
{
return;
}
else
fd = open (name, O_RDONLY | O_BINARY);
if (fd != -1)
{
/* Find the section header information in memory. */
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)addr;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((char *)addr
+ pDosHeader->e_lfanew);
PIMAGE_FILE_HEADER pFileHeader = &pNtHeaders->FileHeader;
DWORD PointerToStringTable = pFileHeader->PointerToSymbolTable
+ (pFileHeader->NumberOfSymbols*0x12);
PIMAGE_SECTION_HEADER sect_hdr =
(PIMAGE_SECTION_HEADER)((char *)&pNtHeaders->OptionalHeader
+ pFileHeader->SizeOfOptionalHeader);
/* Loop through all the section headers, looking for one whose
name is ".vtable_map_vars". */
for (int i = 0; i < pFileHeader->NumberOfSections && !found; ++i)
{
char header_name[64];
/* Check if we have to get the section name from the COFF string
table. */
if (sect_hdr[i].Name[0] == '/')
{
if (atoi((const char*)sect_hdr[i].Name+1) == 0)
{
continue;
}
off_t name_offset = PointerToStringTable
+ atoi((const char*)sect_hdr[i].Name+1);
size_t bytes_read = ReadFromOffset (fd, &header_name, 64,
name_offset);
VTV_ASSERT (bytes_read > 0);
}
else
{
memcpy (&header_name, sect_hdr[i].Name,
sizeof (sect_hdr[i].Name));
}
if (memcmp (header_name, sect_name, name_len) == 0)
{
/* We found the section; get its load offset and
size. */
*sect_offset = sect_hdr[i].VirtualAddress;
if (sect_hdr[i].Misc.VirtualSize % VTV_PAGE_SIZE != 0)
*sect_len = sect_hdr[i].Misc.VirtualSize + VTV_PAGE_SIZE
- (sect_hdr[i].Misc.VirtualSize % VTV_PAGE_SIZE);
else
*sect_len = sect_hdr[i].Misc.VirtualSize;
found = true;
}
}
close (fd);
}
}
if (*sect_offset != 0 && *sect_len != 0)
{
/* Calculate the page location in memory, making sure the
address is page-aligned. */
uintptr_t start_addr = addr + *sect_offset;
*sect_offset = start_addr & ~(VTV_PAGE_SIZE - 1);
*sect_len = *sect_len - 1;
/* Since we got this far, we must not have found these pages in
the cache, so add them to it. NOTE: We could get here either
while making everything read-only or while making everything
read-write. We will only update the cache if we get here on
a read-write (to make absolutely sure the cache is writable
-- also the read-write pass should come before the read-only
pass). */
if ((mprotect_flags & PROT_WRITE)
&& num_cache_entries < MAX_ENTRIES)
{
vtv_sect_info_cache[num_cache_entries].dlpi_addr = addr;
vtv_sect_info_cache[num_cache_entries].mp_low = *sect_offset;
vtv_sect_info_cache[num_cache_entries].mp_size = *sect_len;
num_cache_entries++;
}
}
}
#else
static void
read_section_offset_and_length (struct dl_phdr_info *info,
const char *sect_name,
int mprotect_flags,
off_t *sect_offset,
ElfW (Word) *sect_len)
{
char program_name[PATH_MAX];
char *cptr;
bool found = false;
struct sect_hdr_data *cached_data = NULL;
const ElfW (Phdr) *phdr_info = info->dlpi_phdr;
const ElfW (Ehdr) *ehdr_info =
(const ElfW (Ehdr) *) (info->dlpi_addr + info->dlpi_phdr[0].p_vaddr
- info->dlpi_phdr[0].p_offset);
/* Get the name of the main executable. This may or may not include
arguments passed to the program. Find the first space, assume it
is the start of the argument list, and change it to a '\0'. */
#ifdef HAVE_GETEXECNAME
program_invocation_name = getexecname ();
#endif
snprintf (program_name, sizeof (program_name), program_invocation_name);
/* Check to see if we already have the data for this file. */
cached_data = search_cached_file_data (info->dlpi_addr);
if (cached_data)
{
*sect_offset = cached_data->mp_low;
*sect_len = cached_data->mp_size;
return;
}
/* Find the first non-escaped space in the program name and make it
the end of the string. */
cptr = strchr (program_name, ' ');
if (cptr != NULL && cptr[-1] != '\\')
cptr[0] = '\0';
if ((phdr_info->p_type == PT_PHDR || phdr_info->p_type == PT_LOAD)
&& (ehdr_info->e_shoff && ehdr_info->e_shnum))
{
int name_len = strlen (sect_name);
int fd = -1;
/* Attempt to open the binary file on disk. */
if (strlen (info->dlpi_name) == 0)
{
/* If the constructor initialization function was put into
the preinit array, then this function will get called
while handling preinit array stuff, in which case
program_invocation_name has not been initialized. In
that case we can get the filename of the executable from
"/proc/self/exe". */
if (strlen (program_name) > 0)
{
if (phdr_info->p_type == PT_PHDR)
fd = open (program_name, O_RDONLY);
}
else
fd = open ("/proc/self/exe", O_RDONLY);
}
else
fd = open (info->dlpi_name, O_RDONLY);
if (fd != -1)
{
/* Find the section header information in the file. */
ElfW (Half) strtab_idx = ehdr_info->e_shstrndx;
ElfW (Shdr) shstrtab;
off_t shstrtab_offset = ehdr_info->e_shoff +
(ehdr_info->e_shentsize * strtab_idx);
size_t bytes_read = ReadFromOffset (fd, &shstrtab, sizeof (shstrtab),
shstrtab_offset);
VTV_ASSERT (bytes_read == sizeof (shstrtab));
ElfW (Shdr) sect_hdr;
/* This code will be needed once we have crated libvtv.so. */
bool is_libvtv = false;
/*
if (strstr (info->dlpi_name, "libvtv.so"))
is_libvtv = true;
*/
/* Loop through all the section headers, looking for one whose
name is ".vtable_map_vars". */
for (int i = 0; i < ehdr_info->e_shnum && !found; ++i)
{
off_t offset = ehdr_info->e_shoff + (ehdr_info->e_shentsize * i);
bytes_read = ReadFromOffset (fd, §_hdr, sizeof (sect_hdr),
offset);
VTV_ASSERT (bytes_read == sizeof (sect_hdr));
char header_name[64];
off_t name_offset = shstrtab.sh_offset + sect_hdr.sh_name;
bytes_read = ReadFromOffset (fd, &header_name, 64, name_offset);
VTV_ASSERT (bytes_read > 0);
if (memcmp (header_name, sect_name, name_len) == 0)
{
/* We found the section; get its load offset and
size. */
*sect_offset = sect_hdr.sh_addr;
if (!is_libvtv)
{
VTV_ASSERT (sect_hdr.sh_size - VTV_PAGE_SIZE >= 0);
*sect_len = sect_hdr.sh_size - VTV_PAGE_SIZE;
}
else
*sect_len = sect_hdr.sh_size;
found = true;
}
}
close (fd);
}
}
if (*sect_offset != 0 && *sect_len != 0)
{
/* Calculate the page location in memory, making sure the
address is page-aligned. */
ElfW (Addr) start_addr = (const ElfW (Addr)) info->dlpi_addr
+ *sect_offset;
*sect_offset = start_addr & ~(VTV_PAGE_SIZE - 1);
*sect_len = *sect_len - 1;
/* Since we got this far, we must not have found these pages in
the cache, so add them to it. NOTE: We could get here either
while making everything read-only or while making everything
read-write. We will only update the cache if we get here on
a read-write (to make absolutely sure the cache is writable
-- also the read-write pass should come before the read-only
pass). */
if ((mprotect_flags & PROT_WRITE)
&& num_cache_entries < MAX_ENTRIES)
{
vtv_sect_info_cache[num_cache_entries].dlpi_addr = info->dlpi_addr;
vtv_sect_info_cache[num_cache_entries].mp_low = *sect_offset;
vtv_sect_info_cache[num_cache_entries].mp_size = *sect_len;
num_cache_entries++;
}
}
}
#endif
#if defined (__CYGWIN__) || defined (__MINGW32__)
/* This function is used to iterate over all loaded modules and searches
for a section called ".vtable_map_vars". The only interaction with
the binary file on disk of the module is to read section names in the
COFF string table. If the module contains a ".vtable_map_vars" section,
read section offset and size from the section header of the loaded module.
Call 'mprotect' on those pages, setting the protection either to
read-only or read-write, depending on what's in data.
The calls to change the protection occur in vtv_unprotect_vtable_vars
and vtv_protect_vtable_vars. */
static int
iterate_modules (void *data)
{
int * mprotect_flags = (int *) data;
off_t map_sect_offset = 0;
WORD map_sect_len = 0;
char buffer[1024];
const char *map_sect_name = VTV_PROTECTED_VARS_SECTION;
HMODULE hMods[1024];
HANDLE hProcess;
DWORD cbNeeded;
hProcess = GetCurrentProcess ();
if (NULL == hProcess)
return 0;
if (EnumProcessModules (hProcess, hMods, sizeof (hMods), &cbNeeded))
{
/* Iterate over all loaded modules. */
for (unsigned int i = 0; i < (cbNeeded / sizeof (HMODULE)); i++)
{
char szModName[MAX_PATH];
if (GetModuleFileNameExA (hProcess, hMods[i], szModName,
sizeof (szModName)))
{
map_sect_offset = 0;
map_sect_len = 0;
read_section_offset_and_length (szModName,
(uintptr_t) hMods[i],
map_sect_name,
*mprotect_flags,
&map_sect_offset,
&map_sect_len);
if (debug_functions)
{
snprintf (buffer, sizeof(buffer),
" Looking at load module %s to change permissions to %s\n",
szModName,
(*mprotect_flags & PROT_WRITE) ? "READ/WRITE" : "READ-ONLY");
log_memory_protection_data (buffer);
}
/* See if we actually found the section. */
if (map_sect_offset && map_sect_len)
{
unsigned long long start;
int result;
if (debug_functions)
{
snprintf (buffer, sizeof (buffer),
" (%s): Protecting %p to %p\n",
szModName,
(void *) map_sect_offset,
(void *) (map_sect_offset + map_sect_len));
log_memory_protection_data (buffer);
}
/* Change the protections on the pages for the section. */
start = get_cycle_count ();
result = mprotect ((void *) map_sect_offset, map_sect_len,
*mprotect_flags);
accumulate_cycle_count (&mprotect_cycles, start);
if (result == -1)
{
if (debug_functions)
{
snprintf (buffer, sizeof (buffer),
"Failed call to mprotect for %s error: ",
(*mprotect_flags & PROT_WRITE) ?
"READ/WRITE" : "READ-ONLY");
log_memory_protection_data (buffer);
perror(NULL);
}
VTV_error();
}
else
{
if (debug_functions)
{
snprintf (buffer, sizeof (buffer),
"mprotect'ed range [%p, %p]\n",
(void *) map_sect_offset,
(char *) map_sect_offset + map_sect_len);
log_memory_protection_data (buffer);
}
}
increment_num_calls (&num_calls_to_mprotect);
num_pages_protected += (map_sect_len + VTV_PAGE_SIZE - 1)
/ VTV_PAGE_SIZE;
continue;
}
}
}
}
CloseHandle(hProcess);
return 0;
}
#else
/* This is the callback function used by dl_iterate_phdr (which is
called from vtv_unprotect_vtable_vars and vtv_protect_vtable_vars).
It attempts to find the binary file on disk for the INFO record
that dl_iterate_phdr passes in; open the binary file, and read its
section header information. If the file contains a
".vtable_map_vars" section, read the section offset and size. Use
the section offset and size, in conjunction with the data in INFO
to locate the pages in memory where the section is. Call
'mprotect' on those pages, setting the protection either to
read-only or read-write, depending on what's in DATA. */
static int
dl_iterate_phdr_callback (struct dl_phdr_info *info, size_t, void *data)
{
int * mprotect_flags = (int *) data;
off_t map_sect_offset = 0;
ElfW (Word) map_sect_len = 0;
char buffer[1024];
char program_name[1024];
const char *map_sect_name = VTV_PROTECTED_VARS_SECTION;
/* Check to see if this is the record for the Linux Virtual Dynamic
Shared Object (linux-vdso.so.1), which exists only in memory (and
therefore cannot be read from disk). */
if (strcmp (info->dlpi_name, "linux-vdso.so.1") == 0)
return 0;
if (strlen (info->dlpi_name) == 0
&& info->dlpi_addr != 0)
return 0;
/* Get the name of the main executable. This may or may not include
arguments passed to the program. Find the first space, assume it
is the start of the argument list, and change it to a '\0'. */
#ifdef HAVE_GETEXECNAME
program_invocation_name = getexecname ();
#endif
snprintf (program_name, sizeof (program_name), program_invocation_name);
read_section_offset_and_length (info, map_sect_name, *mprotect_flags,
&map_sect_offset, &map_sect_len);
if (debug_functions)
{
snprintf (buffer, sizeof(buffer),
" Looking at load module %s to change permissions to %s\n",
((strlen (info->dlpi_name) == 0) ? program_name
: info->dlpi_name),
(*mprotect_flags & PROT_WRITE) ? "READ/WRITE" : "READ-ONLY");
log_memory_protection_data (buffer);
}
/* See if we actually found the section. */
if (map_sect_offset && map_sect_len)
{
unsigned long long start;
int result;
if (debug_functions)
{
snprintf (buffer, sizeof (buffer),
" (%s): Protecting %p to %p\n",
((strlen (info->dlpi_name) == 0) ? program_name
: info->dlpi_name),
(void *) map_sect_offset,
(void *) (map_sect_offset + map_sect_len));
log_memory_protection_data (buffer);
}
/* Change the protections on the pages for the section. */
start = get_cycle_count ();
result = mprotect ((void *) map_sect_offset, map_sect_len,
*mprotect_flags);
accumulate_cycle_count (&mprotect_cycles, start);
if (result == -1)
{
if (debug_functions)
{
snprintf (buffer, sizeof (buffer),
"Failed call to mprotect for %s error: ",
(*mprotect_flags & PROT_WRITE) ?
"READ/WRITE" : "READ-ONLY");
log_memory_protection_data (buffer);
perror(NULL);
}
VTV_error();
}
else
{
if (debug_functions)
{
snprintf (buffer, sizeof (buffer),
"mprotect'ed range [%p, %p]\n",
(void *) map_sect_offset,
(char *) map_sect_offset + map_sect_len);
log_memory_protection_data (buffer);
}
}
increment_num_calls (&num_calls_to_mprotect);
num_pages_protected += (map_sect_len + VTV_PAGE_SIZE - 1) / VTV_PAGE_SIZE;
}
return 0;
}
#endif
/* This function explicitly changes the protection (read-only or read-write)
on the vtv_sect_info_cache, which is used for speeding up look ups in the
function dl_iterate_phdr_callback. This data structure needs to be
explicitly made read-write before any calls to dl_iterate_phdr_callback,
because otherwise it may still be read-only when dl_iterate_phdr_callback
attempts to write to it.
More detailed explanation: dl_iterate_phdr_callback finds all the
.vtable_map_vars sections in all loaded objects (including the main program)
and (depending on where it was called from) either makes all the pages in the
sections read-write or read-only. The vtv_sect_info_cache should be in the
.vtable_map_vars section for libstdc++.so, which means that normally it would
be read-only until libstdc++.so is processed by dl_iterate_phdr_callback
(on the read-write pass), after which it will be writable. But if any loaded
object gets processed before libstdc++.so, it will attempt to update the
data cache, which will still be read-only, and cause a seg fault. Hence
we need a special function, called before dl_iterate_phdr_callback, that
will make the data cache writable. */
static void
change_protections_on_phdr_cache (int protection_flag)
{
char * low_address = (char *) &(vtv_sect_info_cache);
size_t cache_size = MAX_ENTRIES * sizeof (struct sect_hdr_data);
low_address = (char *) ((uintptr_t) low_address & ~(VTV_PAGE_SIZE - 1));
if (mprotect ((void *) low_address, cache_size, protection_flag) == -1)
VTV_error ();
}
/* Unprotect all the vtable map vars and other side data that is used
to keep the core hash_map data. All of these data have been put
into relro sections */
static void
vtv_unprotect_vtable_vars (void)
{
int mprotect_flags;
mprotect_flags = PROT_READ | PROT_WRITE;
change_protections_on_phdr_cache (mprotect_flags);
#if defined (__CYGWIN__) || defined (__MINGW32__)
iterate_modules ((void *) &mprotect_flags);
#else
dl_iterate_phdr (dl_iterate_phdr_callback, (void *) &mprotect_flags);
#endif
}
/* Protect all the vtable map vars and other side data that is used
to keep the core hash_map data. All of these data have been put
into relro sections */
static void
vtv_protect_vtable_vars (void)