summaryrefslogtreecommitdiff
path: root/gcc/gimple-harden-conditionals.cc
blob: 19ceb8a4bd61e00caf345e1342fd5c1844a87733 (plain)
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
/* Harden conditionals.
   Copyright (C) 2021-2022 Free Software Foundation, Inc.
   Contributed by Alexandre Oliva <oliva@adacore.com>.

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.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "target.h"
#include "rtl.h"
#include "tree.h"
#include "fold-const.h"
#include "gimple.h"
#include "gimplify.h"
#include "tree-pass.h"
#include "ssa.h"
#include "gimple-iterator.h"
#include "tree-cfg.h"
#include "basic-block.h"
#include "cfghooks.h"
#include "cfgloop.h"
#include "tree-eh.h"
#include "diagnostic.h"
#include "intl.h"

namespace {

/* These passes introduces redundant, but reversed conditionals at
   compares, such as those used in conditional branches, and those
   that compute boolean results.  This doesn't make much sense for
   abstract CPUs, but this kind of hardening may avoid undesirable
   execution paths on actual CPUs under such attacks as of power
   deprivation.  */

/* Define a pass to harden conditionals other than branches.  */

const pass_data pass_data_harden_compares = {
  GIMPLE_PASS,
  "hardcmp",
  OPTGROUP_NONE,
  TV_NONE,
  PROP_cfg | PROP_ssa, // properties_required
  0,	    // properties_provided
  0,	    // properties_destroyed
  0,	    // properties_start
  TODO_update_ssa
  | TODO_cleanup_cfg
  | TODO_verify_il, // properties_finish
};

class pass_harden_compares : public gimple_opt_pass
{
public:
  pass_harden_compares (gcc::context *ctxt)
    : gimple_opt_pass (pass_data_harden_compares, ctxt)
  {}
  opt_pass *clone () { return new pass_harden_compares (m_ctxt); }
  virtual bool gate (function *) {
    return flag_harden_compares;
  }
  virtual unsigned int execute (function *);
};

/* Define a pass to harden conditionals in branches.  This pass must
   run after the above, otherwise it will re-harden the checks
   introduced by the above.  */

const pass_data pass_data_harden_conditional_branches = {
  GIMPLE_PASS,
  "hardcbr",
  OPTGROUP_NONE,
  TV_NONE,
  PROP_cfg | PROP_ssa, // properties_required
  0,	    // properties_provided
  0,	    // properties_destroyed
  0,	    // properties_start
  TODO_update_ssa
  | TODO_cleanup_cfg
  | TODO_verify_il, // properties_finish
};

class pass_harden_conditional_branches : public gimple_opt_pass
{
public:
  pass_harden_conditional_branches (gcc::context *ctxt)
    : gimple_opt_pass (pass_data_harden_conditional_branches, ctxt)
  {}
  opt_pass *clone () { return new pass_harden_conditional_branches (m_ctxt); }
  virtual bool gate (function *) {
    return flag_harden_conditional_branches;
  }
  virtual unsigned int execute (function *);
};

}

/* If VAL is an SSA name, return an SSA name holding the same value,
   but without the compiler's knowing that it holds the same value, so
   that uses thereof can't be optimized the way VAL might.  Insert
   stmts that initialize it before *GSIP, with LOC.

   Otherwise, VAL must be an invariant, returned unchanged.  */

static inline tree
detach_value (location_t loc, gimple_stmt_iterator *gsip, tree val)
{
  if (TREE_CONSTANT (val) || TREE_CODE (val) != SSA_NAME)
    {
      gcc_checking_assert (is_gimple_min_invariant (val));
      return val;
    }

  /* Create a SSA "copy" of VAL.  It would be nice to have it named
     after the corresponding variable, but sharing the same decl is
     problematic when VAL is a DECL_BY_REFERENCE RESULT_DECL, and
     copying just the identifier hits -fcompare-debug failures.  */
  tree ret = make_ssa_name (TREE_TYPE (val));

  /* Some modes won't fit in general regs, so we fall back to memory
     for them.  ??? It would be ideal to try to identify an alternate,
     wider or more suitable register class, and use the corresponding
     constraint, but there's no logic to go from register class to
     constraint, even if there is a corresponding constraint, and even
     if we could enumerate constraints, we can't get to their string
     either.  So this will do for now.  */
  bool need_memory = true;
  enum machine_mode mode = TYPE_MODE (TREE_TYPE (val));
  if (mode != BLKmode)
    for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
      if (TEST_HARD_REG_BIT (reg_class_contents[GENERAL_REGS], i)
	  && targetm.hard_regno_mode_ok (i, mode))
	{
	  need_memory = false;
	  break;
	}

  tree asminput = val;
  tree asmoutput = ret;
  const char *constraint_out = need_memory ? "=m" : "=g";
  const char *constraint_in = need_memory ? "m" : "0";

  if (need_memory)
    {
      tree temp = create_tmp_var (TREE_TYPE (val), "dtch");
      mark_addressable (temp);

      gassign *copyin = gimple_build_assign (temp, asminput);
      gimple_set_location (copyin, loc);
      gsi_insert_before (gsip, copyin, GSI_SAME_STMT);

      asminput = asmoutput = temp;
    }

  /* Output an asm statement with matching input and output.  It does
     nothing, but after it the compiler no longer knows the output
     still holds the same value as the input.  */
  vec<tree, va_gc> *inputs = NULL;
  vec<tree, va_gc> *outputs = NULL;
  vec_safe_push (outputs,
		 build_tree_list
		 (build_tree_list
		  (NULL_TREE, build_string (strlen (constraint_out),
					    constraint_out)),
		  asmoutput));
  vec_safe_push (inputs,
		 build_tree_list
		 (build_tree_list
		  (NULL_TREE, build_string (strlen (constraint_in),
					    constraint_in)),
		  asminput));
  gasm *detach = gimple_build_asm_vec ("", inputs, outputs,
				       NULL, NULL);
  gimple_set_location (detach, loc);
  gsi_insert_before (gsip, detach, GSI_SAME_STMT);

  if (need_memory)
    {
      gassign *copyout = gimple_build_assign (ret, asmoutput);
      gimple_set_location (copyout, loc);
      gsi_insert_before (gsip, copyout, GSI_SAME_STMT);
      SSA_NAME_DEF_STMT (ret) = copyout;

      gassign *clobber = gimple_build_assign (asmoutput,
					      build_clobber
					      (TREE_TYPE (asmoutput)));
      gimple_set_location (clobber, loc);
      gsi_insert_before (gsip, clobber, GSI_SAME_STMT);
    }
  else
    SSA_NAME_DEF_STMT (ret) = detach;

  return ret;
}

/* Build a cond stmt out of COP, LHS, RHS, insert it before *GSIP with
   location LOC.  *GSIP must be at the end of a basic block.  The succ
   edge out of the block becomes the true or false edge opposite to
   that in FLAGS.  Create a new block with a single trap stmt, in the
   cold partition if the function is partitioned,, and a new edge to
   it as the other edge for the cond.  */

static inline void
insert_check_and_trap (location_t loc, gimple_stmt_iterator *gsip,
		       int flags, enum tree_code cop, tree lhs, tree rhs)
{
  basic_block chk = gsi_bb (*gsip);

  gcond *cond = gimple_build_cond (cop, lhs, rhs, NULL, NULL);
  gimple_set_location (cond, loc);
  gsi_insert_before (gsip, cond, GSI_SAME_STMT);

  basic_block trp = create_empty_bb (chk);

  gimple_stmt_iterator gsit = gsi_after_labels (trp);
  gcall *trap = gimple_build_call (builtin_decl_explicit (BUILT_IN_TRAP), 0);
  gimple_set_location (trap, loc);
  gsi_insert_before (&gsit, trap, GSI_SAME_STMT);

  if (dump_file)
    fprintf (dump_file,
	     "Adding reversed compare to block %i, and trap to block %i\n",
	     chk->index, trp->index);

  if (BB_PARTITION (chk))
    BB_SET_PARTITION (trp, BB_COLD_PARTITION);

  int true_false_flag = flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE);
  gcc_assert (true_false_flag);
  int neg_true_false_flag = (~flags) & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE);

  /* Remove the fallthru bit, and set the truth value for the
     preexisting edge and for the newly-created one.  In hardcbr,
     FLAGS is taken from the edge of the original cond expr that we're
     dealing with, so the reversed compare is expected to yield the
     negated result, and the same result calls for a trap.  In
     hardcmp, we're comparing the boolean results of the original and
     of the reversed compare, so we're passed FLAGS to trap on
     equality.  */
  single_succ_edge (chk)->flags &= ~EDGE_FALLTHRU;
  single_succ_edge (chk)->flags |= neg_true_false_flag;
  single_succ_edge (chk)->probability = profile_probability::always ();
  edge e = make_edge (chk, trp, true_false_flag);
  e->goto_locus = loc;
  e->probability = profile_probability::never ();

  if (dom_info_available_p (CDI_DOMINATORS))
    set_immediate_dominator (CDI_DOMINATORS, trp, chk);
  if (current_loops)
    add_bb_to_loop (trp, current_loops->tree_root);
}

/* Split edge E, and insert_check_and_trap (see above) in the
   newly-created block, using detached copies of LHS's and RHS's
   values (see detach_value above) for the COP compare.  */

static inline void
insert_edge_check_and_trap (location_t loc, edge e,
			    enum tree_code cop, tree lhs, tree rhs)
{
  int flags = e->flags;
  basic_block src = e->src;
  basic_block dest = e->dest;
  location_t eloc = e->goto_locus;

  basic_block chk = split_edge (e);
  e = NULL;

  single_pred_edge (chk)->goto_locus = loc;
  single_succ_edge (chk)->goto_locus = eloc;

  if (dump_file)
    fprintf (dump_file,
	     "Splitting edge %i->%i into block %i\n",
	     src->index, dest->index, chk->index);

  gimple_stmt_iterator gsik = gsi_after_labels (chk);

  bool same_p = (lhs == rhs);
  lhs = detach_value (loc, &gsik, lhs);
  rhs = same_p ? lhs : detach_value (loc, &gsik, rhs);

  insert_check_and_trap (loc, &gsik, flags, cop, lhs, rhs);
}

/* Harden cond stmts at the end of FUN's blocks.  */

unsigned int
pass_harden_conditional_branches::execute (function *fun)
{
  basic_block bb;
  FOR_EACH_BB_REVERSE_FN (bb, fun)
    {
      gimple_stmt_iterator gsi = gsi_last_bb (bb);

      if (gsi_end_p (gsi))
	continue;

      gcond *cond = dyn_cast <gcond *> (gsi_stmt (gsi));
      if (!cond)
	continue;

      /* Turn:

	 if (x op y) goto l1; else goto l2;

	 into:

	 if (x op y) goto l1'; else goto l2';
	 l1': if (x' cop y') goto l1'trap; else goto l1;
	 l1'trap: __builtin_trap ();
	 l2': if (x' cop y') goto l2; else goto l2'trap;
	 l2'trap: __builtin_trap ();

	 where cop is a complementary boolean operation to op; l1', l1'trap,
	 l2' and l2'trap are newly-created labels; and x' and y' hold the same
	 value as x and y, but in a way that does not enable the compiler to
	 optimize the redundant compare away.
      */

      enum tree_code op = gimple_cond_code (cond);
      tree lhs = gimple_cond_lhs (cond);
      tree rhs = gimple_cond_rhs (cond);
      location_t loc = gimple_location (cond);

      enum tree_code cop = invert_tree_comparison (op, HONOR_NANS (lhs));

      if (cop == ERROR_MARK)
	/* ??? Can we do better?  */
	continue;

      insert_edge_check_and_trap (loc, EDGE_SUCC (bb, 0), cop, lhs, rhs);
      insert_edge_check_and_trap (loc, EDGE_SUCC (bb, 1), cop, lhs, rhs);
    }

  return 0;
}

/* Instantiate a hardcbr pass.  */

gimple_opt_pass *
make_pass_harden_conditional_branches (gcc::context *ctxt)
{
  return new pass_harden_conditional_branches (ctxt);
}

/* Return the fallthru edge of a block whose other edge is an EH
   edge.  If EHP is not NULL, store the EH edge in it.  */
static inline edge
non_eh_succ_edge (basic_block bb, edge *ehp = NULL)
{
  gcc_checking_assert (EDGE_COUNT (bb->succs) == 2);

  edge ret = find_fallthru_edge (bb->succs);

  int eh_idx = EDGE_SUCC (bb, 0) == ret;
  edge eh = EDGE_SUCC (bb, eh_idx);

  gcc_checking_assert (!(ret->flags & EDGE_EH)
		       && (eh->flags & EDGE_EH));

  if (ehp)
    *ehp = eh;

  return ret;
}

/* Harden boolean-yielding compares in FUN.  */

unsigned int
pass_harden_compares::execute (function *fun)
{
  basic_block bb;
  /* Go backwards over BBs and stmts, so that, even if we split the
     block multiple times to insert a cond_expr after each compare we
     find, we remain in the same block, visiting every preexisting
     stmt exactly once, and not visiting newly-added blocks or
     stmts.  */
  FOR_EACH_BB_REVERSE_FN (bb, fun)
    for (gimple_stmt_iterator gsi = gsi_last_bb (bb);
	 !gsi_end_p (gsi); gsi_prev (&gsi))
      {
	gassign *asgn = dyn_cast <gassign *> (gsi_stmt (gsi));
	if (!asgn)
	  continue;

	/* Turn:

	   z = x op y;

	   into:

	   z = x op y;
	   z' = x' cop y';
	   if (z == z') __builtin_trap ();

	   where cop is a complementary boolean operation to op; and x'
	   and y' hold the same value as x and y, but in a way that does
	   not enable the compiler to optimize the redundant compare
	   away.
	*/

	enum tree_code op = gimple_assign_rhs_code (asgn);

	enum tree_code cop;

	switch (op)
	  {
	  case EQ_EXPR:
	  case NE_EXPR:
	  case GT_EXPR:
	  case GE_EXPR:
	  case LT_EXPR:
	  case LE_EXPR:
	  case LTGT_EXPR:
	  case UNEQ_EXPR:
	  case UNGT_EXPR:
	  case UNGE_EXPR:
	  case UNLT_EXPR:
	  case UNLE_EXPR:
	  case ORDERED_EXPR:
	  case UNORDERED_EXPR:
	    cop = invert_tree_comparison (op,
					  HONOR_NANS
					  (gimple_assign_rhs1 (asgn)));

	    if (cop == ERROR_MARK)
	      /* ??? Can we do better?  */
	      continue;

	    break;

	    /* ??? Maybe handle these too?  */
	  case TRUTH_NOT_EXPR:
	    /* ??? The code below assumes binary ops, it would have to
	       be adjusted for TRUTH_NOT_EXPR, since it's unary.  */
	  case TRUTH_ANDIF_EXPR:
	  case TRUTH_ORIF_EXPR:
	  case TRUTH_AND_EXPR:
	  case TRUTH_OR_EXPR:
	  case TRUTH_XOR_EXPR:
	  default:
	    continue;
	  }

	/* These are the operands for the verification.  */
	tree lhs = gimple_assign_lhs (asgn);
	tree op1 = gimple_assign_rhs1 (asgn);
	tree op2 = gimple_assign_rhs2 (asgn);
	location_t loc = gimple_location (asgn);

	/* Vector booleans can't be used in conditional branches.  ???
	   Can we do better?  How to reduce compare and
	   reversed-compare result vectors to a single boolean?  */
	if (VECTOR_TYPE_P (TREE_TYPE (op1)))
	  continue;

	/* useless_type_conversion_p enables conversions from 1-bit
	   integer types to boolean to be discarded.  */
	gcc_checking_assert (TREE_CODE (TREE_TYPE (lhs)) == BOOLEAN_TYPE
			     || (INTEGRAL_TYPE_P (TREE_TYPE (lhs))
				 && TYPE_PRECISION (TREE_TYPE (lhs)) == 1));

	tree rhs = copy_ssa_name (lhs);

	gimple_stmt_iterator gsi_split = gsi;
	/* Don't separate the original assignment from debug stmts
	   that might be associated with it, and arrange to split the
	   block after debug stmts, so as to make sure the split block
	   won't be debug stmts only.  */
	gsi_next_nondebug (&gsi_split);

	bool throwing_compare_p = stmt_ends_bb_p (asgn);
	if (throwing_compare_p)
	  {
	    basic_block nbb = split_edge (non_eh_succ_edge
					  (gimple_bb (asgn)));
	    gsi_split = gsi_start_bb (nbb);

	    if (dump_file)
	      fprintf (dump_file,
		       "Splitting non-EH edge from block %i into %i"
		       " after a throwing compare\n",
		       gimple_bb (asgn)->index, nbb->index);
	  }

	bool same_p = (op1 == op2);
	op1 = detach_value (loc, &gsi_split, op1);
	op2 = same_p ? op1 : detach_value (loc, &gsi_split, op2);

	gassign *asgnck = gimple_build_assign (rhs, cop, op1, op2);
	gimple_set_location (asgnck, loc);
	gsi_insert_before (&gsi_split, asgnck, GSI_SAME_STMT);

	/* We wish to insert a cond_expr after the compare, so arrange
	   for it to be at the end of a block if it isn't, and for it
	   to have a single successor in case there's more than
	   one, as in PR104975.  */
	if (!gsi_end_p (gsi_split)
	    || !single_succ_p (gsi_bb (gsi_split)))
	  {
	    if (!gsi_end_p (gsi_split))
	      gsi_prev (&gsi_split);
	    else
	      gsi_split = gsi_last_bb (gsi_bb (gsi_split));
	    basic_block obb = gsi_bb (gsi_split);
	    basic_block nbb = split_block (obb, gsi_stmt (gsi_split))->dest;
	    gsi_next (&gsi_split);
	    gcc_checking_assert (gsi_end_p (gsi_split));

	    single_succ_edge (bb)->goto_locus = loc;

	    if (dump_file)
	      fprintf (dump_file,
		       "Splitting block %i into %i"
		       " before the conditional trap branch\n",
		       obb->index, nbb->index);
	  }

	/* If the check assignment must end a basic block, we can't
	   insert the conditional branch in the same block, so split
	   the block again, and prepare to insert the conditional
	   branch in the new block.

	   Also assign an EH region to the compare.  Even though it's
	   unlikely that the hardening compare will throw after the
	   original compare didn't, the compiler won't even know that
	   it's the same compare operands, so add the EH edge anyway.  */
	if (throwing_compare_p)
	  {
	    add_stmt_to_eh_lp (asgnck, lookup_stmt_eh_lp (asgn));
	    make_eh_edges (asgnck);

	    edge ckeh;
	    basic_block nbb = split_edge (non_eh_succ_edge
					  (gimple_bb (asgnck), &ckeh));
	    gsi_split = gsi_start_bb (nbb);

	    if (dump_file)
	      fprintf (dump_file,
		       "Splitting non-EH edge from block %i into %i after"
		       " the newly-inserted reversed throwing compare\n",
		       gimple_bb (asgnck)->index, nbb->index);

	    if (!gimple_seq_empty_p (phi_nodes (ckeh->dest)))
	      {
		edge aseh;
		non_eh_succ_edge (gimple_bb (asgn), &aseh);

		gcc_checking_assert (aseh->dest == ckeh->dest);

		for (gphi_iterator psi = gsi_start_phis (ckeh->dest);
		     !gsi_end_p (psi); gsi_next (&psi))
		  {
		    gphi *phi = psi.phi ();
		    add_phi_arg (phi, PHI_ARG_DEF_FROM_EDGE (phi, aseh), ckeh,
				 gimple_phi_arg_location_from_edge (phi, aseh));
		  }

		if (dump_file)
		  fprintf (dump_file,
			   "Copying PHI args in EH block %i from %i to %i\n",
			   aseh->dest->index, aseh->src->index, ckeh->src->index);
	      }
	  }

	gcc_checking_assert (single_succ_p (gsi_bb (gsi_split)));

	insert_check_and_trap (loc, &gsi_split, EDGE_TRUE_VALUE,
			       EQ_EXPR, lhs, rhs);
      }

  return 0;
}

/* Instantiate a hardcmp pass.  */

gimple_opt_pass *
make_pass_harden_compares (gcc::context *ctxt)
{
  return new pass_harden_compares (ctxt);
}