Module CFCsecurity


Proof of security of the CFC counter-measure

Require Import Coqlib List Maps Integers Errors.
Import ListNotations.
Require Import AST Linking Smallstep Events.
Require Import Globalenvs Values Kildall.
Require Import Op Registers Memory RTL.
Require Import CounterMeasures CFC CFCproof.
Require Import RTLtyping RTLpast RTLfault.

Robustness of signatures

Section compute_sigs.

  Lemma compute_sigs_robust f : forall sigs joins pc c rs pc1 pc2 pred,
      compute_sigs f = OK (sigs, joins) ->
      (fn_code f)!pc = Some (Icond c rs pc1 pc2 pred) ->
      pc1 <> pc2 ->
      get_sig sigs pc1 <> get_sig sigs pc2.
  Proof.
    intros * SIGS FIND NEQ. unfold compute_sigs, robust_sigs in *.
    destruct ext_compute_sigs. repeat monadInv. clear EQ. destruct x0.
    revert EQ1 FIND.
    eapply PTree_Properties.fold_rec.
    - intros * EQ. rewrite EQ; auto.
    - intros * _ EMPTY.
      rewrite PTree.gempty in EMPTY. congruence.
    - intros * NONE FIND IND MON SET.
      repeat monadInv. destruct x0; subst.
      rewrite PTree.gsspec in SET. destruct (peq _ _); [inv SET|].
      + unfold get_sig. cases_eq; repeat monadInv.
        apply Bool.orb_true_iff in EQ2 as [EQ2|EQ2].
        * destruct (peq pc1 pc2); inv EQ2; congruence.
        * apply Bool.negb_true_iff in EQ2. destruct Int.eq_dec; inv EQ2; auto.
      + apply IND; auto.
  Qed.

End compute_sigs.

Static specification

Ltac extlia := unfold rts, gsr in *; Coqlib.extlia.

Ltac destruct_disjs :=
  repeat match goal with H: _ \/ _ |- _ => destruct H end.

Lemma transf_code_cond_inv f tf env : forall pc c rs pc1 pc2 pred,
    wt_function f env ->
    transf_function f = OK tf ->
    do_protect f = true ->
    let code := fn_code tf in
    code!pc = Some (Icond c rs pc1 pc2 pred) ->
    (exists sigs joins abort pcf c' rs' pc1' pc2' pred',
        (fn_code f)!pcf = Some (Icond c' rs' pc1' pc2' pred')
        /\ compute_sigs f = OK (sigs, joins)
        /\ spec_seq_inv (fun pc => pc <> fn_entrypoint tf) abort code
            pc (Icond c rs pc1 pc2 pred)
            pcf (transf_cond sigs (gsr f) (rts f) pcf c' rs' pc1' pc2' pred') (CTwoExits pc1' pc2')
        /\ code!abort = Some (Ibuiltin EF_cm_catch [] BR_none abort))
    \/ (exists sigs joins abort pcf r,
          (fn_code f)!pcf = Some (Ireturn r)
          /\ compute_sigs f = OK (sigs, joins)
          /\ spec_seq_inv (fun pc => pc <> fn_entrypoint tf) abort code
              pc (Icond c rs pc1 pc2 pred) pcf (transf_return sigs (gsr f) pcf r) CNoExit
          /\ code!abort = Some (Ibuiltin EF_cm_catch [] BR_none abort)
      ).
Proof.
  intros * WT TR HARDEN ? FIND; subst code. repeat monadInv.
  rewrite HARDEN in EQ. repeat monadInv.
  destruct CounterMeasures.transf_function eqn:TR. eapply transf_function_inv in TR; eauto.
  2-12:intros; autounfold with rtlcm; try destruct is_join; try destruct Compopts.intra_cfc_return; repeat constructor.
  destruct TR as (?&?&?). simpl in *. destruct_disjs; destruct_conjs; eauto 14.
  all:(inv_transf_function;
       repeat
         match goal with
         | H: context [is_join _ _] |- _ => destruct is_join
         | H: context [Compopts.intra_cfc_return _] |- _ => destruct Compopts.intra_cfc_return
         end; inv_transf_function).
Qed.

Lemma transf_function_protect : forall f tf,
    transf_function f = OK tf ->
    do_protect tf = do_protect f.
Proof.
  intros. unfold do_protect. repeat monadInv. reflexivity.
Qed.

Section CFC.

Variable prog: program.
Variable tprog: program.
Hypothesis TRANSL: match_prog prog tprog.
Let ge := Genv.globalenv prog.
Let tge := Genv.globalenv tprog.

Lemma functions_translated_inv:
  forall v tf,
  Genv.find_funct tge v = Some tf ->
  exists f, Genv.find_funct ge v = Some f /\ transf_fundef f = OK tf.
Proof.
  apply (Genv.find_funct_transf_partial_inv TRANSL).
Qed.

Lemma function_ptr_translated_inv:
  forall v tf,
  Genv.find_funct_ptr tge v = Some tf ->
  exists f, Genv.find_funct_ptr ge v = Some f /\ transf_fundef f = OK tf.
Proof.
  apply (Genv.find_funct_ptr_transf_partial_inv TRANSL).
Qed.

Lemma find_function_translated_inv:
  forall ros rs tfd,
  find_function tge ros rs = Some tfd ->
  exists fd, find_function ge ros rs = Some fd /\ transf_fundef fd = OK tfd.
Proof.
  unfold find_function; intros. destruct ros as [r|id].
  eapply functions_translated_inv; eauto.
  unfold ge, tge in *.
  erewrite <-symbols_preserved; eauto. destruct (Genv.find_symbol _ id); try congruence.
  eapply function_ptr_translated_inv; eauto.
Qed.

Hypothesis WT: wt_program prog.

Ltac external_call_nofault :=
  exfalso;
  repeat
    match goal with
    | H:external_call _ _ _ _ [_] _ _ |- _ =>
        eapply external_call_nofault in H; inv H
    | H:forall f, _ <> _ |- False => eapply H; eauto
    end.

Ltac simplify_xors :=
  match goal with
  | EQ: ?x = Vint _ |- context [?x] => rewrite EQ; simpl
  | EQ: ?x = Vint _, H: context [?x] |- _ => rewrite EQ in H; simpl in H
  | |- context [Int.xor ?x (Int.xor ?x _)] =>
      rewrite <- Int.xor_assoc, Int.xor_idem, Int.xor_zero_l; simpl
  | H:context [Int.xor ?x (Int.xor ?x _)] |- _ =>
      rewrite <- Int.xor_assoc, Int.xor_idem, Int.xor_zero_l in H; simpl in H
  end.

Ltac simplify_tests :=
  match goal with
  | |- context [Int.eq ?x ?x] => rewrite Int.eq_true; simpl
  | |- context [Int.eq _ _] => rewrite Int.eq_false by (intros ?; eauto); simpl
  end.

Ltac imply_condition :=
  match goal with
  | H: eval_condition _ ?rs _ = Some _ |- eval_condition _ ?rs' _ = Some _ =>
      replace rs' with rs; eauto;
      apply map_ext_in; intros * IN; rewrite Regmap.gso; auto
  | H1: eval_condition _ ?rs _ = Some _, H2: eval_condition _ ?rs' _ = Some _ |- _ =>
      replace rs' with rs in H2;
      [rewrite H1 in H2; inv H2|apply map_ext_in; intros * IN; rewrite Regmap.gso; auto]
  end.

Ltac do_step :=
  match goal with
  | H: _ ! ?pc = Some (Inop _) |- step _ (RTL.State _ _ _ ?pc _ _) _ _ =>
      eapply RTL.exec_Inop; [eauto]; simpl; eauto
  | H: _ ! ?pc = Some (Iop _ _ _ _) |- step _ (RTL.State _ _ _ ?pc _ _) _ _ =>
      eapply RTL.exec_Iop; [now eauto|]; simpl; eauto
  | H: _ ! ?pc = Some (Ibuiltin _ _ _ _) |- step _ (RTL.State _ _ _ ?pc _ _) _ _ =>
      eapply RTL.exec_Ibuiltin; eauto; [|constructor]; repeat econstructor
  | H: _ ! ?pc = Some (Icond _ _ _ _ _) |- step _ (RTL.State _ _ _ ?pc _ _) _ _ =>
      eapply RTL.exec_Icond; eauto; simpl; try imply_condition; simpl
  | |- fstep _ _ _ (Valid _) =>
      instantiate (1:=RTL.State _ _ _ _ _ _); simpl
  | H: _ ! ?pc = Some (Inop _) |- fstep _ (Valid (State _ _ _ ?pc _ _)) _ _ =>
      constructor
  | H: _ ! ?pc = Some (Iop _ _ _ _) |- fstep _ (Valid (State _ _ _ ?pc _ _)) _ _ =>
      constructor
  | H: _ ! ?pc = Some (Ibuiltin _ _ _ _) |- fstep _ (Valid (RTL.State _ _ _ ?pc _ _)) _ _ =>
      constructor
  | H: _ ! ?pc = Some (Icond _ _ _ _ _) |- fstep _ (Valid (State _ _ _ ?pc _ _)) _ _ =>
      constructor
  | H: _ ! ?pc = Some (Ibuiltin EF_cm_catch _ _ _) |- fstep _ (Valid (State _ _ _ ?pc _ _)) _ _ =>
      eapply exec_IbuiltinCatch; eauto
  end.

Tactic Notation "star_step" int_or_var(n) :=
  do n (try (eapply star_left; eauto using Eapp_E0)); try apply star_refl.

Ltac simplify_regs :=
  match goal with
  | |- context [(_ # ?x <- _) # ?x] => rewrite Regmap.gss
  | |- context [(_ # _ <- _) # _] => rewrite Regmap.gso by auto
  | H: context [(_ # ?x <- _) # ?x] |- _ => rewrite Regmap.gss in H
  | H: context [(_ # _ <- _) # _] |- _ => rewrite Regmap.gso in H by auto
  end.

Ltac simplify_ctx :=
  simpl in *;
  match goal with
  | H:Some _ = Some _ |- _ => inv H
  | H:Vint _ = Vint _ |- _ => inv H
  | _ => (simplify_regs||simplify_xors||simplify_tests)
  end.

Opaque transf_function.

Actual security proof. First, handle the fault step

Ltac inv_equalities :=
  match goal with
  | H1: ?x = Some _, H2: ?x = Some _ |- _ => rewrite H2 in H1; inv H1
  | H1: ?x = OK _, H2: ?x = OK _ |- _ => rewrite H2 in H1; inv H1
  end.

Ltac inv_gsr :=
  simpl in *;
  repeat
    match goal with
    | H: eval_assert_args _ _ _ _ [_] _ |- _ =>
        inversion H as [|???? ?EVAL ?NIL]; subst; clear H; inv NIL; inv EVAL
    | H: Val.cmp_bool Ceq ?rs # (gsr ?f) _ = Some true |- _ =>
        simpl in H; destruct (rs # (gsr f)) eqn:GSR; inversion H as [EQ];
        apply Int.same_if_eq in EQ; subst; auto
    end.

Lemma fault_step: forall st0 t0 st1 (attacked: function) st2,
    do_protect attacked = true ->
    RTL.initial_state tprog st0 ->
    star step tge st0 t0 st1 ->
    fstep tge (Valid st1) [Event_fault (Fault_invertcond attacked)] st2 ->
    gets_caught tprog st2
    \/ (exists (st' : RTL.state) (t' : trace),
          star fstep tge st2 t' (Valid st') /\ nofault t' /\ star step tge st1 t' st').
Proof.
  intros * HARDEN INIT STEPS FSTEP.
  inv FSTEP.
  1:{ apply step_nofault in H0. inv H0.
      specialize (H2 _ eq_refl). inv H2. }
  apply Eqdep.EqdepTheory.inj_pair2 in H0; subst.
  assert (exists b ros, find_function tge b ros = Some (Internal attacked)) as (?&?&FIND).
  { subst tge.
    eapply steps_has_function in STEPS; eauto. simpl; auto. }
  eapply find_function_translated_inv in FIND as (fd&FIND&EQ); eauto.
  destruct fd; repeat monadInv.
  erewrite transf_function_protect in HARDEN; eauto.
  assert (exists env, wt_function f env) as (env&WTF).
  { eapply find_function_inversion in FIND as (?&IN).
    eapply WT in IN. inv IN. eauto. }
  eapply transf_function_entry in HARDEN as ENTRY; eauto. destruct_conjs.
  assert (gsr f <> rts f) by (unfold gsr, rts; lia).

  eapply transf_code_cond_inv in H1 as [|]; eauto; destruct_conjs.

  - (* cond *)
    inv_equalities.

    assert (forall r, In r x8 -> r <> rts f) as NORTS.
    { intros.
      eapply max_reg_function_use in H1; eauto. extlia. }

    inv_transf_function.
    + (* the first test was attacked *)
      assert (rs # (gsr f) = Vint (get_sig x1 x6)) as GSR.
      { do 1 step_back. inv_gsr. }

      destruct (peq x9 x10); subst; [right|left].
      * (* same dest *)
        do 3 esplit; [|split].
        -- destruct b; star_step 6; repeat do_step; simpl; eauto using eval_builtin_args_BA.
           1,3:repeat simplify_ctx; eauto; simpl; eauto.
           1,2:repeat do_step.
        -- constructor.
        -- destruct b; star_step 7. 1-6,8-13:repeat do_step; simpl; eauto using eval_builtin_args_BA; repeat econstructor.
           1,2:repeat simplify_ctx; eauto.
           1,2:repeat do_step.
      * (* different dest *)
        eapply compute_sigs_robust in n; eauto.
        destruct b; star_step 7; repeat do_step; simpl; eauto using eval_builtin_args_BA.
        1,4:repeat simplify_ctx; eauto.
        all:repeat do_step.
    + (* the condition was inverted *)
      assert (rs # (gsr f) = Vint (get_sig x1 x6)) as GSR.
      { do 4 step_back; repeat simplify_ctx; inv_gsr. }
      assert (rs # (rts f) = Vint (Int.xor (get_sig x1 x6) (if b then get_sig x1 x9 else get_sig x1 x10))) as RTS.
      { do 4 step_back; simpl in *.
        1,2:repeat simplify_ctx.
        1,2:assert (b0 = b) by (now imply_condition); subst.
        1,2:destruct b; simpl in *; auto; try congruence.
      }

      destruct (peq x9 x10); subst; [right|left].
      * (* same dest *)
        do 3 esplit; [|split].
        -- destruct b; star_step 3; repeat do_step; simpl.
           1,3:repeat simplify_ctx; eauto; simpl; eauto.
           all:try congruence; repeat do_step.
        -- constructor.
        -- destruct b; star_step 4. 1-3,5-7:repeat do_step; simpl.
           1,2:repeat simplify_ctx; eauto.
           all:repeat do_step.
      * (* different dest *)
        eapply compute_sigs_robust in n; eauto.
        destruct b; star_step 4; repeat do_step; simpl.
        1,4:repeat simplify_ctx; reflexivity.
        all:repeat do_step.
    + (* the check in the first branch was inverted *)
      assert (b = false); subst.
    { do 6 step_back; inv_gsr.
      all:repeat simplify_ctx; try reflexivity.
      all:destruct (peq x9 x10); subst; repeat simplify_ctx; auto.
      exfalso. eapply compute_sigs_robust in n; eauto.
      imply_condition.
      destruct b1; subst; try congruence.
      assert (Int.xor (get_sig x1 x6) (get_sig x1 x9) <> Int.xor (get_sig x1 x6) (get_sig x1 x10)); [|congruence].
      intros EQ; apply Int.xor_inj_r in EQ; congruence.
    }

    left.
    star_step 2; repeat do_step.

    + (* the check in the second branch was inverted *)
      assert (b = false); subst.
      { do 6 step_back; inv_gsr.
        all:repeat simplify_ctx; try reflexivity.
        all:destruct (peq x9 x10); subst; repeat simplify_ctx; auto.
        exfalso. eapply compute_sigs_robust in n; eauto.
        imply_condition.
        destruct b1; subst; try congruence.
        assert (Int.xor (get_sig x1 x6) (get_sig x1 x9) <> Int.xor (get_sig x1 x6) (get_sig x1 x10)); [|congruence].
        intros EQ; apply Int.xor_inj_r in EQ; congruence.
      }

      left.
      star_step 2; repeat do_step.

  - (* return, it will get caught ! *)
    inv_transf_function; destruct Compopts.intra_cfc_return; inv_transf_function.
    do 1 step_back. inv_gsr. simpl in *. rewrite Int.eq_true in H2; inv H2.

    left. star_step 2; repeat do_step.
Qed.

Theorem transf_program_secure: forall (attacked: function),
    do_protect attacked = true ->
    secure_against tprog (Fault_invertcond attacked).
Proof.
  intros ??? INIT FSTEP FAULT.
  eapply protected_multi_step with (t1:=E0); eauto using fault_step.
  constructor.
Qed.

End CFC.