Skip to content
Permalink
Browse files

[trusted-types] Initial implementation of trust checking

Summary:
Adds the initial core algorithm for trusted types. When the --trust-mode (cli) / trust_mode (.flowconfig) flag is set to "check", the `trust_flow` function in `flow_js.ml` will be invoked on every flow constraint. This function ignores the types of the lower and upper bounds of the constraint, but instead looks at their trust, and raises an error if the trust is incompatible, using the new `trust_subtype` function in `trust.ml`. (Future diffs improve the error messages.)

This change necessitates a couple other interesting changes. The largest one is that `quick_subtype` in `type.ml` now has to be trust-aware, so that trust enforcement is not short-circuited away. However, we only want to change its behavior when trust is enabled, so we pass in a boolean argument to determine whether trust should be checked.

This diff also adds a bunch of test cases, including some that currently do *not* behave as expected: these are currently located in the /tests/trust/_failing/ subdirectory, which is ignored by the .flowconfig.

Reviewed By: dsainati1

Differential Revision: D13874693

fbshipit-source-id: 8e8165a12f5246d391443c287fff720e80e2035d
  • Loading branch information...
mvitousek authored and facebook-github-bot committed Mar 21, 2019
1 parent e001ccb commit 959b4bad08ebf9fb2c2d4446653b8192bd0eb7d8
@@ -1063,12 +1063,17 @@ module M__flow
functions `rec_flow`, `join_flow`, or `flow_opt` (described below) inside
this module, and the function `flow` outside this module. **)
let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
if ground_subtype (l, u) then

if ground_subtype (l, u) then begin
if Context.trust_mode cx <> Options.NoTrust then
trust_flow_to_use_t cx trace l u;
print_types_if_verbose cx trace (l, u)
else if Cache.FlowConstraint.get cx (l, u) then
end else if Cache.FlowConstraint.get cx (l, u) then
print_types_if_verbose cx trace ~note:"(cached)" (l, u)
else (
print_types_if_verbose cx trace (l, u);
if Context.trust_mode cx <> Options.NoTrust then
trust_flow_to_use_t cx trace l u;

(* limit recursion depth *)
RecursionCheck.check trace;
@@ -2888,17 +2893,19 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
| _, _ ->
let ts2 = Type_mapper.union_flatten cx @@ UnionRep.members rep2 in
Type_mapper.union_flatten cx @@ UnionRep.members rep1
|> List.for_all (fun t1 -> List.exists (TypeUtil.quick_subtype t1) ts2)
|> List.for_all (fun t1 -> List.exists
(TypeUtil.quick_subtype (Context.trust_mode cx = Options.CheckTrust) t1) ts2)
end ->
()

(* Optimization to treat maybe and optional types as special unions for subset comparision *)

| DefT (reason, _, UnionT rep), UseT (use_op, DefT (r, _, MaybeT maybe)) ->
let checked_trust = Context.trust_mode cx = Options.CheckTrust in
let void = (VoidT.why r |> with_trust bogus_trust) in
let null = (NullT.why r |> with_trust bogus_trust) in
let filter_void t = TypeUtil.quick_subtype t void in
let filter_null t = TypeUtil.quick_subtype t null in
let filter_void t = TypeUtil.quick_subtype checked_trust t void in
let filter_null t = TypeUtil.quick_subtype checked_trust t null in
let filter_null_and_void t = filter_void t || filter_null t in
let remove_predicate predicate =
UnionRep.members
@@ -2907,7 +2914,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
%> union_of_ts reason in
(* if the union doesn't contain void or null,
then everything in it must be upper-bounded by maybe *)
begin match UnionRep.quick_mem_enum void rep, UnionRep.quick_mem_enum null rep with
begin match UnionRep.quick_mem_enum checked_trust void rep, UnionRep.quick_mem_enum checked_trust null rep with
| UnionRep.No, UnionRep.No -> rec_flow_t ~use_op cx trace (l, maybe)
| UnionRep.Yes, UnionRep.No ->
rec_flow_t ~use_op cx trace (remove_predicate filter_void rep, maybe)
@@ -2919,14 +2926,15 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
end

| DefT (reason, _, UnionT rep), UseT (use_op, DefT (r, _, OptionalT opt)) ->
let checked_trust = Context.trust_mode cx = Options.CheckTrust in
let void = (VoidT.why r |> with_trust bogus_trust) in
let remove_void =
UnionRep.members
%> Type_mapper.union_flatten cx
%> Core_list.rev_filter ~f:(fun t -> TypeUtil.quick_subtype t void |> not)
%> Core_list.rev_filter ~f:(fun t -> TypeUtil.quick_subtype checked_trust t void |> not)
%> union_of_ts reason in
(* if the union doesn't contain void, then everything in it must be upper-bounded by u *)
begin match UnionRep.quick_mem_enum void rep with
begin match UnionRep.quick_mem_enum checked_trust void rep with
| UnionRep.No -> rec_flow_t ~use_op cx trace (l, opt)
| UnionRep.Yes -> rec_flow_t ~use_op cx trace (remove_void rep, opt)
| _ -> UnionRep.members rep |> List.iter (fun t -> rec_flow cx trace (t, u))
@@ -2944,7 +2952,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
| Enum.Bool v -> SingletonBoolT v
| Enum.Void -> VoidT
| Enum.Null -> NullT in
match UnionRep.quick_mem_enum (DefT (r, trust, def)) rep with
match UnionRep.quick_mem_enum (Context.trust_mode cx = Options.CheckTrust) (DefT (r, trust, def)) rep with
| UnionRep.No -> () (* provably unreachable, so prune *)
| UnionRep.Yes -> rec_flow_t cx trace (l, result)
| UnionRep.Conditional _ | UnionRep.Unknown -> (* inconclusive: the union is not concretized *)
@@ -2958,7 +2966,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
| Enum.Bool v -> SingletonBoolT v
| Enum.Void -> VoidT
| Enum.Null -> NullT in
UnionRep.join_quick_mem_results (acc, UnionRep.quick_mem_enum (DefT (r, trust, def)) rep)
UnionRep.join_quick_mem_results (acc, UnionRep.quick_mem_enum (Context.trust_mode cx = Options.CheckTrust) (DefT (r, trust, def)) rep)
) enums UnionRep.No in
begin match acc with
| UnionRep.No -> () (* provably unreachable, so prune *)
@@ -3023,7 +3031,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =

| _, UseT (_, DefT (_, _, UnionT rep)) when
let ts = Type_mapper.union_flatten cx @@ UnionRep.members rep in
List.exists (TypeUtil.quick_subtype l) ts ->
List.exists (TypeUtil.quick_subtype (Context.trust_mode cx = Options.CheckTrust) l) ts ->
()

| _, UseT (use_op, DefT (r, _, UnionT rep)) ->
@@ -6437,6 +6445,28 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
branches = [];
})
)
and trust_flow_to_use_t cx trace l u =
match u with
| UseT (use_op, u) -> trust_flow cx trace use_op l u
| _ -> ()

and trust_flow cx trace use_op l u =
let check (lr, ltrust) (ur, utrust) =
if Context.trust_mode cx <> Options.SilentTrust
&& not (subtype_trust ltrust utrust) then
add_output cx ~trace (Error_message.EIncompatibleWithUseOp (
lr, ur, use_op
))
in

match l, u with
| DefT (lr, ltrust, _), DefT (ur, utrust, _) ->
check (lr, ltrust) (ur, utrust)
| AnyT (r, _), DefT (ur, utrust, _) ->
check (r, dynamic_trust ()) (ur, utrust)
| DefT (lr, ltrust, _), AnyT (r, _) ->
check (lr, ltrust) (r, dynamic_trust ())
| _ -> ()

(**
* Addition
@@ -8402,11 +8432,11 @@ and optimize_spec_try_shortcut cx trace reason_op = function

and shortcut_enum cx trace reason_op use_op l rep =
quick_mem_result cx trace reason_op use_op l rep @@
UnionRep.quick_mem_enum l rep
UnionRep.quick_mem_enum (Context.trust_mode cx = Options.CheckTrust) l rep

and shortcut_disjoint_union cx trace reason_op use_op l rep =
quick_mem_result cx trace reason_op use_op l rep @@
UnionRep.quick_mem_disjoint_union l rep
UnionRep.quick_mem_disjoint_union (Context.trust_mode cx = Options.CheckTrust) l rep
~find_resolved:(Context.find_resolved cx)
~find_props:(Context.find_props cx)

@@ -84,3 +84,9 @@ let is_tainted = function
let is_public = function
| Dynamic | Initial -> true
| _ -> false

let subtype_trust l u =
match l, u with
| Initial, _
| _, Terminal -> true
| _ -> l = u
@@ -19,3 +19,5 @@ val make_private: trust -> trust

val is_public: trust -> bool
val is_tainted: trust -> bool

val subtype_trust: trust -> trust -> bool
@@ -1554,12 +1554,13 @@ and UnionRep : sig
val join_quick_mem_results: quick_mem_result * quick_mem_result -> quick_mem_result

val quick_mem_enum:
TypeTerm.t ->
bool -> TypeTerm.t ->
t -> quick_mem_result

val quick_mem_disjoint_union:
find_resolved:(TypeTerm.t -> TypeTerm.t option) ->
find_props:(Properties.id -> TypeTerm.property SMap.t) ->
bool ->
TypeTerm.t ->
t -> quick_mem_result

@@ -1798,19 +1799,19 @@ end = struct
| No, No -> No

(* assume we know that l is a canonizable type *)
let quick_mem_enum l (_t0, _t1, _ts, specialization) =
let quick_mem_enum trust_checked l (_t0, _t1, _ts, specialization) =
match canon l with
| Some tcanon ->
begin match !specialization with
| None -> Unknown
| Some Unoptimized -> Unknown
| Some Empty -> No
| Some (Singleton t) ->
if TypeUtil.quick_subtype l t then Yes
if TypeUtil.quick_subtype trust_checked l t then Yes
else Conditional t
| Some (DisjointUnion _) -> No
| Some (PartiallyOptimizedDisjointUnion (_, others)) ->
if Nel.exists (TypeUtil.quick_subtype l) others
if Nel.exists (TypeUtil.quick_subtype trust_checked l) others
then Yes
else Unknown
| Some (Enum tset) ->
@@ -1820,7 +1821,7 @@ end = struct
| Some (PartiallyOptimizedEnum (tset, others)) ->
if EnumSet.mem tcanon tset
then Yes
else if Nel.exists (TypeUtil.quick_subtype l) others
else if Nel.exists (TypeUtil.quick_subtype trust_checked l) others
then Yes
else Unknown
end
@@ -1843,26 +1844,26 @@ end = struct
) map Unknown

(* we know that l is an object type or exact object type *)
let quick_mem_disjoint_union ~find_resolved ~find_props l (_t0, _t1, _ts, specialization) =
let quick_mem_disjoint_union ~find_resolved ~find_props trust_checked l (_t0, _t1, _ts, specialization) =
match props_of find_props l with
| Some prop_map ->
begin match !specialization with
| None -> Unknown
| Some Unoptimized -> Unknown
| Some Empty -> No
| Some (Singleton t) ->
if TypeUtil.quick_subtype l t then Yes
if TypeUtil.quick_subtype trust_checked l t then Yes
else Conditional t
| Some (DisjointUnion map) ->
lookup_disjoint_union find_resolved prop_map ~partial:false map
| Some (PartiallyOptimizedDisjointUnion (map, others)) ->
let result = lookup_disjoint_union find_resolved prop_map ~partial:true map in
if result <> Unknown then result
else if Nel.exists (TypeUtil.quick_subtype l) others then Yes
else if Nel.exists (TypeUtil.quick_subtype trust_checked l) others then Yes
else Unknown
| Some (Enum _) -> No
| Some (PartiallyOptimizedEnum (_, others)) ->
if Nel.exists (TypeUtil.quick_subtype l) others then Yes
if Nel.exists (TypeUtil.quick_subtype trust_checked l) others then Yes
else Unknown
end
| _ -> failwith "quick_mem_disjoint_union is defined only on object / exact object types"
@@ -2157,7 +2158,7 @@ and TypeUtil : sig
val number_literal_eq: TypeTerm.number_literal -> TypeTerm.number_literal TypeTerm.literal -> bool
val boolean_literal_eq: bool -> bool option -> bool

val quick_subtype: TypeTerm.t -> TypeTerm.t -> bool
val quick_subtype: bool -> TypeTerm.t -> TypeTerm.t -> bool
end = struct
open TypeTerm

@@ -2730,8 +2731,8 @@ end = struct
(* In reposition we also recurse and reposition some nested types. We need
* to make sure we swap the types for these reasons as well. Otherwise our
* optimized union ~> union check will not pass. *)
| DefT (_, _, MaybeT t2), DefT (r, _, MaybeT t1) -> DefT (r, Trust.bogus_trust (), MaybeT (swap_reason t2 t1))
| DefT (_, _, OptionalT t2), DefT (r, _, OptionalT t1) -> DefT (r, Trust.bogus_trust (), OptionalT (swap_reason t2 t1))
| DefT (_, trust, MaybeT t2), DefT (r, _, MaybeT t1) -> DefT (r, trust, MaybeT (swap_reason t2 t1))
| DefT (_, trust, OptionalT t2), DefT (r, _, OptionalT t1) -> DefT (r, trust, OptionalT (swap_reason t2 t1))
| ExactT (_, t2), ExactT (r, t1) -> ExactT (r, swap_reason t2 t1)

| _ -> mod_reason_of_t (fun _ -> reason_of_t t1) t2
@@ -2756,22 +2757,24 @@ end = struct
| Some y -> x = y
| None -> false

let quick_subtype t1 t2 =
let quick_subtype trust_checked t1 t2 =
match t1, t2 with
| DefT (_, _, NumT _), DefT (_, _, NumT _)
| DefT (_, _, SingletonNumT _), DefT (_, _, NumT _)
| DefT (_, _, StrT _), DefT (_, _, StrT _)
| DefT (_, _, SingletonStrT _), DefT (_, _, StrT _)
| DefT (_, _, BoolT _), DefT (_, _, BoolT _)
| DefT (_, _, SingletonBoolT _), DefT (_, _, BoolT _)
| DefT (_, _, NullT), DefT (_, _, NullT)
| DefT (_, _, VoidT), DefT (_, _, VoidT)
| DefT (_, _, EmptyT), _
| _, DefT (_, _, MixedT _)
-> true
| DefT (_, _, StrT actual), DefT (_, _, SingletonStrT expected) -> literal_eq expected actual
| DefT (_, _, NumT actual), DefT (_, _, SingletonNumT expected) -> number_literal_eq expected actual
| DefT (_, _, BoolT actual), DefT (_, _, SingletonBoolT expected) -> boolean_literal_eq expected actual
| DefT (_, ltrust, NumT _), DefT (_, rtrust, NumT _)
| DefT (_, ltrust, SingletonNumT _), DefT (_, rtrust, NumT _)
| DefT (_, ltrust, StrT _), DefT (_, rtrust, StrT _)
| DefT (_, ltrust, SingletonStrT _), DefT (_, rtrust, StrT _)
| DefT (_, ltrust, BoolT _), DefT (_, rtrust, BoolT _)
| DefT (_, ltrust, SingletonBoolT _), DefT (_, rtrust, BoolT _)
| DefT (_, ltrust, NullT), DefT (_, rtrust, NullT)
| DefT (_, ltrust, VoidT), DefT (_, rtrust, VoidT)
| DefT (_, ltrust, EmptyT), DefT(_, rtrust, _)
| DefT (_, ltrust, _), DefT (_, rtrust, MixedT _)
-> not trust_checked || Trust.subtype_trust ltrust rtrust
| DefT (_, ltrust, EmptyT), _ -> not trust_checked || Trust.is_public ltrust
| _, DefT (_, rtrust, MixedT _) -> not trust_checked || Trust.is_tainted rtrust
| DefT (_, ltrust, StrT actual), DefT (_, rtrust, SingletonStrT expected) -> Trust.subtype_trust ltrust rtrust && literal_eq expected actual
| DefT (_, ltrust, NumT actual), DefT (_, rtrust, SingletonNumT expected) -> Trust.subtype_trust ltrust rtrust && number_literal_eq expected actual
| DefT (_, ltrust, BoolT actual), DefT (_, rtrust, SingletonBoolT expected) -> Trust.subtype_trust ltrust rtrust && boolean_literal_eq expected actual
| _ -> reasonless_eq t1 t2
end

@@ -0,0 +1,5 @@
[ignore]
.*/_failing/.*

[options]
trust_mode=check
@@ -0,0 +1,28 @@
//@flow

var a = {x: 42};
a.x = ("Hello world": any);

var b: {x: number} = {x: 42};
b.x = ("Hello world": any);

var c: {x: $Trusted<number>} = {x: 42};
c.x = ("Hello world": any); // Error

var d = {x: 42};
d.x = ("Hello world": any);
var e:$Trusted<number> = d.x;
var k: {x: $Trusted<number>} = d;

var h = 42;
var f: {x: typeof h} = {x: 42};
f.x = ("Hello world": any);
var g:$Trusted<number> = f.x;

var i: 42 = 42
var j: $Trusted<42> = i;

var a1 = {x: 42};
var a2: {x: $Trusted<number>} = a1;
var a3: {x: number} = a1;
a3.x = ("Hello world": any);
@@ -0,0 +1,4 @@
//@flow

var x: any = 'Hi';
x += " world";
@@ -0,0 +1,24 @@
// @flow
var x: $Trusted<number> = (42: any); //Fail
var y: $Trusted<number> = 42; //ok
var z: any = y; // ok

function f(x: number): $Trusted<number> { return x; } // Fail

function g(x: $Trusted<number>): $Trusted<number> { return x; } //Ok

function h(x: $Trusted<number>): number { return x; } //Ok

var a = 42; //Ok
var b: number = 42; // ok
var c: $Trusted<number> = 42; // ok

var i: $Trusted<number> = a; //ok
var k: $Trusted<number> = b; // fail
var j: $Trusted<number> = c; // ok
var l: number = c; // ok
var m = c; // ok

var d = 42; // ok
d = ('Hello': any) // ok
var e: $Trusted<number> = d; // fail
@@ -0,0 +1,19 @@
//@flow

function f(x:$Trusted<number>): number { return x; }

var a: ($Trusted<number> => number) = f;
var b: (any => number) = a; //fail
var c: (number=> number) = a; // fails
var d: ($Trusted<number> => any) = a;
var e: any = a; //fail
var h: ($Trusted<number> => $Trusted<number>) = a;

function g(x: number): $Trusted<number> { return 42; }

var x: ($Trusted<number> => number) = g;
var y: (number => $Trusted<number>) = g;
var z: (any => $Trusted<number>) = y;
var w: (number => number) = y;
var u: (number => any) = y;
var v: any = y;
@@ -0,0 +1,4 @@
//@flow
var w: $Trusted<number> = 42;
var x: {y: $Trusted<number>} = {y: w}
var z: any = x;

0 comments on commit 959b4ba

Please sign in to comment.
You can’t perform that action at this time.