GCC 6 ha un flag, -mmitigate-rop
, che compila i binari in un modo che riduce il numero di gadget sfruttabili da ROP. La documentazione GCC che spiega questa funzione è minima:
-mmitigate-rop Try to avoid generating code sequences that contain unintended return opcodes, to mitigate against certain forms of attack. At the moment, this option is limited in what it can do and should not be relied on to provide serious protection.
Sto cercando di capire esattamente come funziona per valutare quanto sia efficace contro ROP. Ho letto il codice sorgente ma è difficile capire senza conoscere gli interni di GCC. La mia impressione è che è piuttosto inutile e banale da bypassare, ma dato che non conosco nemmeno la teoria alla base (chiaramente non è vero il CFI come le mitigazioni ROP di clang), non posso nemmeno cominciare a valutarlo. Forse garantisce che gli opcode che, se letti con un diverso offset, non possano essere decodificati in ret
? Mi piacerebbe sapere come funziona questa funzionalità e quali sono le sue implicazioni per catene ROP realistiche.
Le funzioni che sembrano rilevanti per questa funzione sono copiate di seguito (in nessun ordine particolare).
/* Return true if T is one of the bytes we should avoid with -mmitigate-rop. */ static bool ix86_rop_should_change_byte_p (int t) { return t == 0xc2 || t == 0xc3 || t == 0xca || t == 0xcb; } /* Given an insn INSN with NOPERANDS OPERANDS, return the modr/m byte used in its encoding if it could be relevant for ROP mitigation, otherwise return -1. If POPNO0 and POPNO1 are nonnull, store the operand numbers used for calculating it into them. */ static int ix86_get_modrm_for_rop (rtx_insn *insn, rtx *operands, int noperands, int *popno0 = 0, int *popno1 = 0) { if (asm_noperands (PATTERN (insn)) >= 0) return -1; int has_modrm = get_attr_modrm (insn); if (!has_modrm) return -1; enum attr_modrm_class cls = get_attr_modrm_class (insn); rtx op0, op1; switch (cls) { case MODRM_CLASS_OP02: gcc_assert (noperands >= 3); if (popno0) { *popno0 = 0; *popno1 = 2; } op0 = operands[0]; op1 = operands[2]; break; case MODRM_CLASS_OP01: gcc_assert (noperands >= 2); if (popno0) { *popno0 = 0; *popno1 = 1; } op0 = operands[0]; op1 = operands[1]; break; default: return -1; } if (REG_P (op0) && REG_P (op1)) { int enc0 = reg_encoded_number (op0); int enc1 = reg_encoded_number (op1); return 0xc0 + (enc1 << 3) + enc0; } return -1; } /* Given a register number BASE, the lowest of a group of registers, update regsets IN and OUT with the registers that should be avoided in input and output operands respectively when trying to avoid generating a modr/m byte for -mmitigate-rop. */ static void set_rop_modrm_reg_bits (int base, HARD_REG_SET &in, HARD_REG_SET &out) { SET_HARD_REG_BIT (out, base); SET_HARD_REG_BIT (out, base + 1); SET_HARD_REG_BIT (in, base + 2); SET_HARD_REG_BIT (in, base + 3); } /* Called if -mmitigate-rop is in effect. Try to rewrite instructions so that certain encodings of modr/m bytes do not occur. */ static void ix86_mitigate_rop (void) { HARD_REG_SET input_risky; HARD_REG_SET output_risky; HARD_REG_SET inout_risky; CLEAR_HARD_REG_SET (output_risky); CLEAR_HARD_REG_SET (input_risky); SET_HARD_REG_BIT (output_risky, AX_REG); SET_HARD_REG_BIT (output_risky, CX_REG); SET_HARD_REG_BIT (input_risky, BX_REG); SET_HARD_REG_BIT (input_risky, DX_REG); set_rop_modrm_reg_bits (FIRST_SSE_REG, input_risky, output_risky); set_rop_modrm_reg_bits (FIRST_REX_INT_REG, input_risky, output_risky); set_rop_modrm_reg_bits (FIRST_REX_SSE_REG, input_risky, output_risky); set_rop_modrm_reg_bits (FIRST_EXT_REX_SSE_REG, input_risky, output_risky); set_rop_modrm_reg_bits (FIRST_MASK_REG, input_risky, output_risky); set_rop_modrm_reg_bits (FIRST_BND_REG, input_risky, output_risky); COPY_HARD_REG_SET (inout_risky, input_risky); IOR_HARD_REG_SET (inout_risky, output_risky); df_note_add_problem (); /* Fix up what stack-regs did. */ df_insn_rescan_all (); df_analyze (); regrename_init (true); regrename_analyze (NULL); auto_vec cands; for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn)) { if (!NONDEBUG_INSN_P (insn)) continue; if (GET_CODE (PATTERN (insn)) == USE || GET_CODE (PATTERN (insn)) == CLOBBER) continue; extract_insn (insn); int opno0, opno1; int modrm = ix86_get_modrm_for_rop (insn, recog_data.operand, recog_data.n_operands, &opno0, &opno1); if (!ix86_rop_should_change_byte_p (modrm)) continue; insn_rr_info *info = &insn_rr[INSN_UID (insn)]; /* This happens when regrename has to fail a block. */ if (!info->op_info) continue; if (info->op_info[opno0].n_chains != 0) { gcc_assert (info->op_info[opno0].n_chains == 1); du_head_p op0c; op0c = regrename_chain_from_id (info->op_info[opno0].heads[0]->id); if (op0c->target_data_1 + op0c->target_data_2 == 0 && !op0c->cannot_rename) cands.safe_push (op0c); op0c->target_data_1++; } if (info->op_info[opno1].n_chains != 0) { gcc_assert (info->op_info[opno1].n_chains == 1); du_head_p op1c; op1c = regrename_chain_from_id (info->op_info[opno1].heads[0]->id); if (op1c->target_data_1 + op1c->target_data_2 == 0 && !op1c->cannot_rename) cands.safe_push (op1c); op1c->target_data_2++; } } int i; du_head_p head; FOR_EACH_VEC_ELT (cands, i, head) { int old_reg, best_reg; HARD_REG_SET unavailable; CLEAR_HARD_REG_SET (unavailable); if (head->target_data_1) IOR_HARD_REG_SET (unavailable, output_risky); if (head->target_data_2) IOR_HARD_REG_SET (unavailable, input_risky); int n_uses; reg_class superclass = regrename_find_superclass (head, &n_uses, &unavailable); old_reg = head->regno; best_reg = find_rename_reg (head, superclass, &unavailable, old_reg, false); bool ok = regrename_do_replace (head, best_reg); gcc_assert (ok); if (dump_file) fprintf (dump_file, "Chain %d renamed as %s in %s\n", head->id, reg_names[best_reg], reg_class_names[superclass]); } regrename_finish (); df_analyze (); basic_block bb; regset_head live; INIT_REG_SET (&live); FOR_EACH_BB_FN (bb, cfun) { rtx_insn *insn; COPY_REG_SET (&live, DF_LR_OUT (bb)); df_simulate_initialize_backwards (bb, &live); FOR_BB_INSNS_REVERSE (bb, insn) { if (!NONDEBUG_INSN_P (insn)) continue; df_simulate_one_insn_backwards (bb, insn, &live); if (GET_CODE (PATTERN (insn)) == USE || GET_CODE (PATTERN (insn)) == CLOBBER) continue; extract_insn (insn); constrain_operands_cached (insn, reload_completed); int opno0, opno1; int modrm = ix86_get_modrm_for_rop (insn, recog_data.operand, recog_data.n_operands, &opno0, &opno1); if (modrm %d\n", i); rtx newreg = gen_rtx_REG (recog_data.operand_mode[opno1], i); validate_change (insn, recog_data.operand_loc[opno1], newreg, false); insn = emit_insn_before (gen_move_insn (newreg, oldreg), insn); } } }