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
|
.. _Security_Hardening_Features:
***************************
Security Hardening Features
***************************
This chapter describes Ada extensions aimed at security hardening that
are provided by GNAT.
The features in this chapter are currently experimental and subject to
change.
.. Register Scrubbing:
Register Scrubbing
==================
GNAT can generate code to zero-out hardware registers before returning
from a subprogram.
It can be enabled with the :switch:`-fzero-call-used-regs={choice}`
command-line option, to affect all subprograms in a compilation, and
with a :samp:`Machine_Attribute` pragma, to affect only specific
subprograms.
.. code-block:: ada
procedure Foo;
pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
-- Before returning, Foo scrubs only call-clobbered registers
-- that it uses itself.
function Bar return Integer;
pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
-- Before returning, Bar scrubs all call-clobbered registers.
function Baz return Integer;
pragma Machine_Attribute (Bar, "zero_call_used_regs", "leafy");
-- Before returning, Bar scrubs call-clobbered registers, either
-- those it uses itself, if it can be identified as a leaf
-- function, or all of them otherwise.
For usage and more details on the command-line option, on the
``zero_call_used_regs`` attribute, and on their use with other
programming languages, see :title:`Using the GNU Compiler Collection
(GCC)`.
.. Stack Scrubbing:
Stack Scrubbing
===============
GNAT can generate code to zero-out stack frames used by subprograms.
It can be activated with the :samp:`Machine_Attribute` pragma, on
specific subprograms and variables, or their types. (This attribute
always applies to a type, even when it is associated with a subprogram
or a variable.)
.. code-block:: ada
function Foo returns Integer;
pragma Machine_Attribute (Foo, "strub");
-- Foo and its callers are modified so as to scrub the stack
-- space used by Foo after it returns. Shorthand for:
-- pragma Machine_Attribute (Foo, "strub", "at-calls");
procedure Bar;
pragma Machine_Attribute (Bar, "strub", "internal");
-- Bar is turned into a wrapper for its original body,
-- and they scrub the stack used by the original body.
Var : Integer;
pragma Machine_Attribute (Var, "strub");
-- Reading from Var in a subprogram enables stack scrubbing
-- of the stack space used by the subprogram. Furthermore, if
-- Var is declared within a subprogram, this also enables
-- scrubbing of the stack space used by that subprogram.
Given these declarations, Foo has its type and body modified as
follows:
.. code-block:: ada
function Foo (<WaterMark> : in out System.Address) returns Integer
is
-- ...
begin
<__strub_update> (<WaterMark>); -- Updates the stack WaterMark.
-- ...
end;
whereas its callers are modified from:
.. code-block:: ada
X := Foo;
to:
.. code-block:: ada
declare
<WaterMark> : System.Address;
begin
<__strub_enter> (<WaterMark>); -- Initialize <WaterMark>.
X := Foo (<WaterMark>);
<__strub_leave> (<WaterMark>); -- Scrubs stack up to <WaterMark>.
end;
As for Bar, because it is strubbed in internal mode, its callers are
not modified. Its definition is modified roughly as follows:
.. code-block:: ada
procedure Bar is
<WaterMark> : System.Address;
procedure Strubbed_Bar (<WaterMark> : in out System.Address) is
begin
<__strub_update> (<WaterMark>); -- Updates the stack WaterMark.
-- original Bar body.
end Strubbed_Bar;
begin
<__strub_enter> (<WaterMark>); -- Initialize <WaterMark>.
Strubbed_Bar (<WaterMark>);
<__strub_leave> (<WaterMark>); -- Scrubs stack up to <WaterMark>.
end Bar;
There are also :switch:`-fstrub={choice}` command-line options to
control default settings. For usage and more details on the
command-line options, on the ``strub`` attribute, and their use with
other programming languages, see :title:`Using the GNU Compiler
Collection (GCC)`.
Note that Ada secondary stacks are not scrubbed. The restriction
``No_Secondary_Stack`` avoids their use, and thus their accidental
preservation of data that should be scrubbed.
Attributes ``Access`` and ``Unconstrained_Access`` of variables and
constants with ``strub`` enabled require types with ``strub`` enabled;
there is no way to express an access-to-strub type otherwise.
``Unchecked_Access`` bypasses this constraint, but the resulting
access type designates a non-strub type.
.. code-block:: ada
VI : aliased Integer;
pragma Machine_Attribute (VI, "strub");
XsVI : access Integer := VI'Access; -- Error.
UXsVI : access Integer := VI'Unchecked_Access; -- OK,
-- UXsVI does *not* enable strub in subprograms that
-- dereference it to obtain the UXsVI.all value.
type Strub_Int is new Integer;
pragma Machine_Attribute (Strub_Int, "strub");
VSI : aliased Strub_Int;
XsVSI : access Strub_Int := VSI'Access; -- OK,
-- VSI and XsVSI.all both enable strub in subprograms that
-- read their values.
Every access-to-subprogram type, renaming, and overriding and
overridden dispatching operations that may refer to a subprogram with
an attribute-modified interface must be annotated with the same
interface-modifying attribute. Access-to-subprogram types can be
explicitly converted to different strub modes, as long as they are
interface-compatible (i.e., adding or removing ``at-calls`` is not
allowed). For example, a ``strub``-``disabled`` subprogram can be
turned ``callable`` through such an explicit conversion:
.. code-block:: ada
type TBar is access procedure;
type TBar_Callable is access procedure;
pragma Machine_Attribute (TBar_Callable, "strub", "callable");
-- The attribute modifies the procedure type, rather than the
-- access type, because of the extra argument after "strub",
-- only applicable to subprogram types.
Bar_Callable_Ptr : constant TBar_Callable
:= TBar_Callable (TBar'(Bar'Access));
procedure Bar_Callable renames Bar_Callable_Ptr.all;
pragma Machine_Attribute (Bar_Callable, "strub", "callable");
Note that the renaming declaration is expanded to a full subprogram
body, it won't be just an alias. Only if it is inlined will it be as
efficient as a call by dereferencing the access-to-subprogram constant
Bar_Callable_Ptr.
.. Hardened Conditionals:
Hardened Conditionals
=====================
GNAT can harden conditionals to protect against control-flow attacks.
This is accomplished by two complementary transformations, each
activated by a separate command-line option.
The option :switch:`-fharden-compares` enables hardening of compares
that compute results stored in variables, adding verification that the
reversed compare yields the opposite result, turning:
.. code-block:: ada
B := X = Y;
into:
.. code-block:: ada
B := X = Y;
declare
NotB : Boolean := X /= Y; -- Computed independently of B.
begin
if B = NotB then
<__builtin_trap>;
end if;
end;
The option :switch:`-fharden-conditional-branches` enables hardening
of compares that guard conditional branches, adding verification of
the reversed compare to both execution paths, turning:
.. code-block:: ada
if X = Y then
X := Z + 1;
else
Y := Z - 1;
end if;
into:
.. code-block:: ada
if X = Y then
if X /= Y then -- Computed independently of X = Y.
<__builtin_trap>;
end if;
X := Z + 1;
else
if X /= Y then -- Computed independently of X = Y.
null;
else
<__builtin_trap>;
end if;
Y := Z - 1;
end if;
These transformations are introduced late in the compilation pipeline,
long after boolean expressions are decomposed into separate compares,
each one turned into either a conditional branch or a compare whose
result is stored in a boolean variable or temporary. Compiler
optimizations, if enabled, may also turn conditional branches into
stored compares, and vice-versa, or into operations with implied
conditionals (e.g. MIN and MAX). Conditionals may also be optimized
out entirely, if their value can be determined at compile time, and
occasionally multiple compares can be combined into one.
It is thus difficult to predict which of these two options will affect
a specific compare operation expressed in source code. Using both
options ensures that every compare that is neither optimized out nor
optimized into implied conditionals will be hardened.
The addition of reversed compares can be observed by enabling the dump
files of the corresponding passes, through command-line options
:switch:`-fdump-tree-hardcmp` and :switch:`-fdump-tree-hardcbr`,
respectively.
They are separate options, however, because of the significantly
different performance impact of the hardening transformations.
For usage and more details on the command-line options, see
:title:`Using the GNU Compiler Collection (GCC)`. These options can
be used with other programming languages supported by GCC.
.. Hardened Booleans:
Hardened Booleans
=================
Ada has built-in support for introducing boolean types with
alternative representations, using representation clauses:
.. code-block:: ada
type HBool is new Boolean;
for HBool use (16#5a#, 16#a5#);
for HBool'Size use 8;
When validity checking is enabled, the compiler will check that
variables of such types hold values corresponding to the selected
representations.
There are multiple strategies for where to introduce validity checking
(see :switch:`-gnatV` options). Their goal is to guard against
various kinds of programming errors, and GNAT strives to omit checks
when program logic rules out an invalid value, and optimizers may
further remove checks found to be redundant.
For additional hardening, the ``hardbool`` :samp:`Machine_Attribute`
pragma can be used to annotate boolean types with representation
clauses, so that expressions of such types used as conditions are
checked even when compiling with :switch:`-gnatVT`:
.. code-block:: ada
pragma Machine_Attribute (HBool, "hardbool");
function To_Boolean (X : HBool) returns Boolean is (Boolean (X));
is compiled roughly like:
.. code-block:: ada
function To_Boolean (X : HBool) returns Boolean is
begin
if X not in True | False then
raise Constraint_Error;
elsif X in True then
return True;
else
return False;
end if;
end To_Boolean;
Note that :switch:`-gnatVn` will disable even ``hardbool`` testing.
Analogous behavior is available as a GCC extension to the C and
Objective C programming languages, through the ``hardbool`` attribute,
with the difference that, instead of raising a Constraint_Error
exception, when a hardened boolean variable is found to hold a value
that stands for neither True nor False, the program traps. For usage
and more details on that attribute, see :title:`Using the GNU Compiler
Collection (GCC)`.
.. Control Flow Redundancy:
Control Flow Redundancy
=======================
GNAT can guard against unexpected execution flows, such as branching
into the middle of subprograms, as in Return Oriented Programming
exploits.
In units compiled with :switch:`-fharden-control-flow-redundancy`,
subprograms are instrumented so that, every time they are called,
basic blocks take note as control flows through them, and, before
returning, subprograms verify that the taken notes are consistent with
the control-flow graph.
The performance impact of verification on leaf subprograms can be much
higher, while the averted risks are much lower on them.
Instrumentation can be disabled for leaf subprograms with
:switch:`-fhardcfr-skip-leaf`.
Functions with too many basic blocks, or with multiple return points,
call a run-time function to perform the verification. Other functions
perform the verification inline before returning.
Optimizing the inlined verification can be quite time consuming, so
the default upper limit for the inline mode is set at 16 blocks.
Command-line option :switch:`--param hardcfr-max-inline-blocks=` can
override it.
Even though typically sparse control-flow graphs exhibit run-time
verification time nearly proportional to the block count of a
subprogram, it may become very significant for generated subprograms
with thousands of blocks. Command-line option
:switch:`--param hardcfr-max-blocks=` can set an upper limit for
instrumentation.
For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
Verification is performed just before a subprogram returns. The
following fragment:
.. code-block:: ada
if X then
Y := F (Z);
return;
end if;
gets turned into:
.. code-block:: ada
type Visited_Bitmap is array (1..N) of Boolean with Pack;
Visited : aliased Visited_Bitmap := (others => False);
-- Bitmap of visited blocks. N is the basic block count.
[...]
-- Basic block #I
Visited(I) := True;
if X then
-- Basic block #J
Visited(J) := True;
Y := F (Z);
CFR.Check (N, Visited'Access, CFG'Access);
-- CFR is a hypothetical package whose Check procedure calls
-- libgcc's __hardcfr_check, that traps if the Visited bitmap
-- does not hold a valid path in CFG, the run-time
-- representation of the control flow graph in the enclosing
-- subprogram.
return;
end if;
-- Basic block #K
Visited(K) := True;
Verification would also be performed before tail calls, if any
front-ends marked them as mandatory or desirable, but none do.
Regular calls are optimized into tail calls too late for this
transformation to act on it.
In order to avoid adding verification after potential tail calls,
which would prevent tail-call optimization, we recognize returning
calls, i.e., calls whose result, if any, is returned by the calling
subprogram to its caller immediately after the call returns.
Verification is performed before such calls, whether or not they are
ultimately optimized to tail calls. This behavior is enabled by
default whenever sibcall optimization is enabled (see
:switch:`-foptimize-sibling-calls`); it may be disabled with
:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
:switch:`-fhardcfr-check-returning-calls`, regardless of the
optimization, but the lack of other optimizations may prevent calls
from being recognized as returning calls:
.. code-block:: ada
-- CFR.Check here, with -fhardcfr-check-returning-calls.
P (X);
-- CFR.Check here, with -fno-hardcfr-check-returning-calls.
return;
or:
.. code-block:: ada
-- CFR.Check here, with -fhardcfr-check-returning-calls.
R := F (X);
-- CFR.Check here, with -fno-hardcfr-check-returning-calls.
return R;
Any subprogram from which an exception may escape, i.e., that may
raise or propagate an exception that isn't handled internally, is
conceptually enclosed by a cleanup handler that performs verification,
unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
With this feature enabled, a subprogram body containing:
.. code-block:: ada
-- ...
Y := F (X); -- May raise exceptions.
-- ...
raise E; -- Not handled internally.
-- ...
gets modified as follows:
.. code-block:: ada
begin
-- ...
Y := F (X); -- May raise exceptions.
-- ...
raise E; -- Not handled internally.
-- ...
exception
when others =>
CFR.Check (N, Visited'Access, CFG'Access);
raise;
end;
Verification may also be performed before No_Return calls, whether all
of them, with :switch:`-fhardcfr-check-noreturn-calls=always`; all but
internal subprograms involved in exception-raising or -reraising or
subprograms explicitly marked with both :samp:`No_Return` and
:samp:`Machine_Attribute` :samp:`expected_throw` pragmas, with
:switch:`-fhardcfr-check-noreturn-calls=no-xthrow` (default); only
nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`;
or none, with :switch:`-fhardcfr-check-noreturn-calls=never`.
When a No_Return call returns control to its caller through an
exception, verification may have already been performed before the
call, if :switch:`-fhardcfr-check-noreturn-calls=always` or
:switch:`-fhardcfr-check-noreturn-calls=no-xthrow` is in effect. The
compiler arranges for already-checked No_Return calls without a
preexisting handler to bypass the implicitly-added cleanup handler and
thus the redundant check, but a local exception or cleanup handler, if
present, will modify the set of visited blocks, and checking will take
place again when the caller reaches the next verification point,
whether it is a return or reraise statement after the exception is
otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
:switch:`-fdump-tree-hardcfr`.
For more details on the control flow redundancy command-line options,
see :title:`Using the GNU Compiler Collection (GCC)`. These options
can be used with other programming languages supported by GCC.
|