@@ -40,25 +40,48 @@ def get_ELF_program_headers(executable):
40
40
stdout = run_command ([READELF_CMD , '-l' , '-W' , executable ])
41
41
42
42
in_headers = False
43
- count = 0
44
43
headers = []
45
44
for line in stdout .splitlines ():
46
45
if line .startswith ('Program Headers:' ):
47
46
in_headers = True
47
+ count = 0
48
48
if line == '' :
49
49
in_headers = False
50
50
if in_headers :
51
51
if count == 1 : # header line
52
- ofs_typ = line .find ( 'Type' )
53
- ofs_offset = line . find ( 'Offset ' )
54
- ofs_flags = line . find ('Flg' )
55
- ofs_align = line . find ( 'Align' )
56
- if ofs_typ == - 1 or ofs_offset == - 1 or ofs_flags == - 1 or ofs_align == - 1 :
52
+ header = [ x . strip () for x in line .split ()]
53
+ ofs_typ = header . index ( 'Type ' )
54
+ ofs_flags = header . index ('Flg' )
55
+ # assert readelf output is what we expect
56
+ if ofs_typ == - 1 or ofs_flags == - 1 :
57
57
raise ValueError ('Cannot parse elfread -lW output' )
58
58
elif count > 1 :
59
- typ = line [ofs_typ :ofs_offset ].rstrip ()
60
- flags = line [ofs_flags :ofs_align ].rstrip ()
61
- headers .append ((typ , flags ))
59
+ splitline = [x .strip () for x in line .split ()]
60
+ typ = splitline [ofs_typ ]
61
+ if not typ .startswith ('[R' ): # skip [Requesting ...]
62
+ splitline = [x .strip () for x in line .split ()]
63
+ flags = splitline [ofs_flags ]
64
+ # check for 'R', ' E'
65
+ if splitline [ofs_flags + 1 ] is 'E' :
66
+ flags += ' E'
67
+ headers .append ((typ , flags , []))
68
+ count += 1
69
+
70
+ if line .startswith (' Section to Segment mapping:' ):
71
+ in_mapping = True
72
+ count = 0
73
+ if line == '' :
74
+ in_mapping = False
75
+ if in_mapping :
76
+ if count == 1 : # header line
77
+ ofs_segment = line .find ('Segment' )
78
+ ofs_sections = line .find ('Sections...' )
79
+ if ofs_segment == - 1 or ofs_sections == - 1 :
80
+ raise ValueError ('Cannot parse elfread -lW output' )
81
+ elif count > 1 :
82
+ segment = int (line [ofs_segment :ofs_sections ].strip ())
83
+ sections = line [ofs_sections :].strip ().split ()
84
+ headers [segment ][2 ].extend (sections )
62
85
count += 1
63
86
return headers
64
87
@@ -68,7 +91,7 @@ def check_ELF_NX(executable) -> bool:
68
91
'''
69
92
have_wx = False
70
93
have_gnu_stack = False
71
- for (typ , flags ) in get_ELF_program_headers (executable ):
94
+ for (typ , flags , _ ) in get_ELF_program_headers (executable ):
72
95
if typ == 'GNU_STACK' :
73
96
have_gnu_stack = True
74
97
if 'W' in flags and 'E' in flags : # section is both writable and executable
@@ -82,7 +105,7 @@ def check_ELF_RELRO(executable) -> bool:
82
105
Dynamic section must have BIND_NOW flag
83
106
'''
84
107
have_gnu_relro = False
85
- for (typ , flags ) in get_ELF_program_headers (executable ):
108
+ for (typ , flags , _ ) in get_ELF_program_headers (executable ):
86
109
# Note: not checking flags == 'R': here as linkers set the permission differently
87
110
# This does not affect security: the permission flags of the GNU_RELRO program
88
111
# header are ignored, the PT_LOAD header determines the effective permissions.
@@ -113,6 +136,62 @@ def check_ELF_Canary(executable) -> bool:
113
136
ok = True
114
137
return ok
115
138
139
+ def check_ELF_separate_code (executable ):
140
+ '''
141
+ Check that sections are appropriately separated in virtual memory,
142
+ based on their permissions. This checks for missing -Wl,-z,separate-code
143
+ and potentially other problems.
144
+ '''
145
+ EXPECTED_FLAGS = {
146
+ # Read + execute
147
+ '.init' : 'R E' ,
148
+ '.plt' : 'R E' ,
149
+ '.plt.got' : 'R E' ,
150
+ '.plt.sec' : 'R E' ,
151
+ '.text' : 'R E' ,
152
+ '.fini' : 'R E' ,
153
+ # Read-only data
154
+ '.interp' : 'R' ,
155
+ '.note.gnu.property' : 'R' ,
156
+ '.note.gnu.build-id' : 'R' ,
157
+ '.note.ABI-tag' : 'R' ,
158
+ '.gnu.hash' : 'R' ,
159
+ '.dynsym' : 'R' ,
160
+ '.dynstr' : 'R' ,
161
+ '.gnu.version' : 'R' ,
162
+ '.gnu.version_r' : 'R' ,
163
+ '.rela.dyn' : 'R' ,
164
+ '.rela.plt' : 'R' ,
165
+ '.rodata' : 'R' ,
166
+ '.eh_frame_hdr' : 'R' ,
167
+ '.eh_frame' : 'R' ,
168
+ '.qtmetadata' : 'R' ,
169
+ '.gcc_except_table' : 'R' ,
170
+ '.stapsdt.base' : 'R' ,
171
+ # Writable data
172
+ '.init_array' : 'RW' ,
173
+ '.fini_array' : 'RW' ,
174
+ '.dynamic' : 'RW' ,
175
+ '.got' : 'RW' ,
176
+ '.data' : 'RW' ,
177
+ '.bss' : 'RW' ,
178
+ }
179
+ # For all LOAD program headers get mapping to the list of sections,
180
+ # and for each section, remember the flags of the associated program header.
181
+ flags_per_section = {}
182
+ for (typ , flags , sections ) in get_ELF_program_headers (executable ):
183
+ if typ == 'LOAD' :
184
+ for section in sections :
185
+ assert (section not in flags_per_section )
186
+ flags_per_section [section ] = flags
187
+ # Spot-check ELF LOAD program header flags per section
188
+ # If these sections exist, check them against the expected R/W/E flags
189
+ for (section , flags ) in flags_per_section .items ():
190
+ if section in EXPECTED_FLAGS :
191
+ if EXPECTED_FLAGS [section ] != flags :
192
+ return False
193
+ return True
194
+
116
195
def get_PE_dll_characteristics (executable ) -> int :
117
196
'''Get PE DllCharacteristics bits'''
118
197
stdout = run_command ([OBJDUMP_CMD , '-x' , executable ])
@@ -225,7 +304,8 @@ def check_MACHO_Canary(executable) -> bool:
225
304
('PIE' , check_ELF_PIE ),
226
305
('NX' , check_ELF_NX ),
227
306
('RELRO' , check_ELF_RELRO ),
228
- ('Canary' , check_ELF_Canary )
307
+ ('Canary' , check_ELF_Canary ),
308
+ ('separate_code' , check_ELF_separate_code ),
229
309
],
230
310
'PE' : [
231
311
('DYNAMIC_BASE' , check_PE_DYNAMIC_BASE ),
0 commit comments