From b4948117bb9eee0ba43750f30a82d2179b4f3afc Mon Sep 17 00:00:00 2001 From: Maxime Jacquemin <maxime.jacquemin@cea.fr> Date: Wed, 4 Jan 2023 16:19:02 +0100 Subject: [PATCH] [Ivette] Taints in the AST --- .../frama-c/plugins/eva/api/general/index.ts | 93 ++--- ivette/src/sandbox/codemirror6.tsx | 201 ++++++---- ivette/src/sandbox/dark-code.css | 22 ++ src/plugins/eva/api/general_requests.ml | 360 ++++++++---------- 4 files changed, 357 insertions(+), 319 deletions(-) diff --git a/ivette/src/frama-c/plugins/eva/api/general/index.ts b/ivette/src/frama-c/plugins/eva/api/general/index.ts index ff0ce5ec9b9..596145f3a59 100644 --- a/ivette/src/frama-c/plugins/eva/api/general/index.ts +++ b/ivette/src/frama-c/plugins/eva/api/general/index.ts @@ -219,63 +219,6 @@ const getDeadCode_internal: Server.GetRequest<Json.key<'#fct'>,deadCode> = { /** Get the lists of unreachable and of non terminating statements in a function */ export const getDeadCode: Server.GetRequest<Json.key<'#fct'>,deadCode>= getDeadCode_internal; -const taintedLvalues_internal: Server.GetRequest< - Json.key<'#fundec'>, - { lval: Json.key<'#lval'>, - before: - { data: "direct" | "indirect" | "untainted", - indirect: "direct" | "indirect" | "untainted" }, - after: - { data: "direct" | "indirect" | "untainted", - indirect: "direct" | "indirect" | "untainted" } }[] - > = { - kind: Server.RqKind.GET, - name: 'plugins.eva.general.taintedLvalues', - input: Json.jKey<'#fundec'>('#fundec'), - output: Json.jArray( - Json.jObject({ - lval: Json.jKey<'#lval'>('#lval'), - before: Json.jObject({ - data: Json.jUnion<"direct" | "indirect" | "untainted">( - Json.jTag("direct"), - Json.jTag("indirect"), - Json.jTag("untainted"), - ), - indirect: Json.jUnion<"direct" | "indirect" | - "untainted">( - Json.jTag("direct"), - Json.jTag("indirect"), - Json.jTag("untainted"), - ), - }), - after: Json.jObject({ - data: Json.jUnion<"direct" | "indirect" | "untainted">( - Json.jTag("direct"), - Json.jTag("indirect"), - Json.jTag("untainted"), - ), - indirect: Json.jUnion<"direct" | "indirect" | - "untainted">( - Json.jTag("direct"), - Json.jTag("indirect"), - Json.jTag("untainted"), - ), - }), - })), - signals: [], -}; -/** Get the tainted lvalues of a given function */ -export const taintedLvalues: Server.GetRequest< - Json.key<'#fundec'>, - { lval: Json.key<'#lval'>, - before: - { data: "direct" | "indirect" | "untainted", - indirect: "direct" | "indirect" | "untainted" }, - after: - { data: "direct" | "indirect" | "untainted", - indirect: "direct" | "indirect" | "untainted" } }[] - >= taintedLvalues_internal; - /** Taint status of logical properties */ export enum taintStatus { /** **Not computed:** @@ -314,6 +257,42 @@ const taintStatusTags_internal: Server.GetRequest<null,tag[]> = { /** Registered tags for the above type. */ export const taintStatusTags: Server.GetRequest<null,tag[]>= taintStatusTags_internal; +/** Lvalue taint status */ +export interface LvalueTaints { + /** tainted lvalue */ + lval: Json.key<'#lval'>; + /** taint status */ + taint: taintStatus; +} + +/** Decoder for `LvalueTaints` */ +export const jLvalueTaints: Json.Decoder<LvalueTaints> = + Json.jObject({ lval: Json.jKey<'#lval'>('#lval'), taint: jTaintStatus,}); + +/** Natural order for `LvalueTaints` */ +export const byLvalueTaints: Compare.Order<LvalueTaints> = + Compare.byFields + <{ lval: Json.key<'#lval'>, taint: taintStatus }>({ + lval: Compare.string, + taint: byTaintStatus, + }); + +const taintedLvalues_internal: Server.GetRequest< + Json.key<'#fundec'>, + LvalueTaints[] + > = { + kind: Server.RqKind.GET, + name: 'plugins.eva.general.taintedLvalues', + input: Json.jKey<'#fundec'>('#fundec'), + output: Json.jArray(jLvalueTaints), + signals: [], +}; +/** Get the tainted lvalues of a given function */ +export const taintedLvalues: Server.GetRequest< + Json.key<'#fundec'>, + LvalueTaints[] + >= taintedLvalues_internal; + /** Data for array rows [`properties`](#properties) */ export interface propertiesData { /** Entry identifier. */ diff --git a/ivette/src/sandbox/codemirror6.tsx b/ivette/src/sandbox/codemirror6.tsx index b2336e5450a..67276e47a59 100644 --- a/ivette/src/sandbox/codemirror6.tsx +++ b/ivette/src/sandbox/codemirror6.tsx @@ -6,6 +6,7 @@ import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view'; import { Decoration, DecorationSet } from '@codemirror/view'; import { DOMEventMap as EventMap } from '@codemirror/view'; import { GutterMarker, gutter } from '@codemirror/view'; +import { showTooltip, Tooltip } from '@codemirror/view'; import { parser } from '@lezer/cpp'; import { tags } from '@lezer/highlight'; @@ -18,7 +19,7 @@ import * as Dome from 'dome'; import * as Utils from 'dome/data/arrays'; import * as Server from 'frama-c/server'; import * as States from 'frama-c/states'; -import type { key } from 'dome/data/json'; +import { key } from 'dome/data/json'; import * as Ast from 'frama-c/kernel/api/ast'; import { text } from 'frama-c/kernel/api/data'; import * as Eva from 'frama-c/plugins/eva/api/general'; @@ -35,10 +36,9 @@ import './dark-code.css'; // CodeMirror state's extensions types definitions // ----------------------------------------------------------------------------- -// Helper types definitions +// Helper types definitions. export type Get<A> = (state: EditorState | undefined) => A; export type Set<A> = (view: EditorView | null, value: A) => void; -export type Equal<A> = (left: A, right: A) => boolean; export interface Data<A, S> { init: A, get: Get<A>, structure: S } // Event handlers type definition. @@ -143,12 +143,12 @@ function getExtension<A>(dep: Dependency<A>): Extension { // provided through the optional parameters <equal>. Providing an equality test // for complex types can help improve performances by avoiding recomputing // extensions depending on the field. -export function createField<A>(init: A, equal?: Equal<A>): Field<A> { +export function createField<A>(init: A): Field<A> { const annot = Annotation.define<A>(); const create = (): A => init; type Update<A> = (current: A, transaction: Transaction) => A; const update: Update<A> = (current, tr) => tr.annotation(annot) ?? current; - const field = StateField.define<A>({ create, update, compare: equal }); + const field = StateField.define<A>({ create, update }); const get: Get<A> = (state) => state?.field(field) ?? init; const useSet: Set<A> = (v, a) => React.useEffect(() => v?.dispatch({ annotations: annot.of(a) }), [v, a]); @@ -204,6 +204,23 @@ export function createGutter<I extends Dict>( return enables.concat(extension); } +// A Tooltip is an extension that adds decorations as a floating DOM element +// above or below some text. See the CodeMirror's documentation on Tooltip +// for further details. +export function createTooltip<I extends Dict>( + deps: Dependencies<I>, + fn: (input: I) => Tooltip | Tooltip[] | undefined, +): Extension { + const { structure, extension } = createAspect(deps, fn); + const show = showTooltip.computeN([structure], st => { + const tip = st.facet(structure); + if (tip === undefined) return [null]; + if ('length' in tip) return tip; + return [tip]; + }); + return [extension, show]; +} + // An Event Handler is an extention responsible of performing a computation each // time a DOM event (like <mouseup> or <contextmenu>) happens. export function createEventHandler<I extends Dict>( @@ -223,7 +240,7 @@ export function createEventHandler<I extends Dict>( // ----------------------------------------------------------------------------- -// Utilitary types +// Utilitary types and functions // ----------------------------------------------------------------------------- // An alias type for functions and locations. @@ -237,6 +254,14 @@ type Caller = { fct: key<'#fct'>, marker: key<'#stmt'> }; // A range is just a pair of position in the code. interface Range { from: number, to: number } +// Type checking that an input is defined. +function isDef<A>(a: A | undefined): a is A { return a !== undefined; } + +// Map a function over a list, removing all inputs that returned undefined. +function mapFilter<A, B>(xs: A[], fn: (x: A) => B | undefined): B[] { + return xs.map(fn).filter(isDef); +} + // ----------------------------------------------------------------------------- @@ -246,29 +271,35 @@ interface Range { from: number, to: number } // The code is given by the server has a tree but implemented with arrays and // without information on the ranges of each element. It will be converted in a // good old tree that carry those information. -interface Tree extends Range { id?: string, children: Tree[] } +interface Leaf extends Range { text: string } +interface Node extends Range { id: string, children: Tree[] } +type Tree = Leaf | Node; -// A leaf tree with no children. -const leaf = (from: number, to: number): Tree => ({ from, to, children: [] }); +// Utility functions on trees. +function isLeaf (t: Tree): t is Leaf { return 'text' in t; } +function isNode (t: Tree): t is Node { return 'id' in t && 'children' in t; } +const empty: Tree = { text: '', from: 0, to: 0 }; // Convert an Ivette text (i.e a function's code) into a Tree, adding range // information to each construction. function textToTree(t: text): Tree | undefined { - function aux(t: text, from: number): Tree | undefined { - if (t === null) return undefined; - if (typeof t === 'string') return leaf(from, from + t.length); + function aux(t: text, from: number): [Tree | undefined, number] { + if (t === null) return [undefined, from]; + if (typeof t === 'string') { + const to = from + t.length; + return [{ text: t, from, to }, to]; + } + if (t.length < 2 || typeof t[0] !== 'string') return [undefined, from]; const children: Tree[] = []; let acc = from; for (const child of t.slice(1)) { - const node = aux(child, acc); - if (node) { acc = node.to; children.push(node); } + const [node, to] = aux(child, acc); + if (node) children.push(node); + acc = to; } - if (children.length === 0) return undefined; - const to = children[children.length - 1].to; - const finalFrom = children[0].from; - const id = typeof t[0] === 'string' && t[0][0] === '#' ? t[0] : undefined; - return { id, from: finalFrom, to, children }; + return [{ id: t[0], from, to: acc, children }, acc]; } - return aux(t, 0); + const [res] = aux(t, 0); + return res; } // Convert an Ivette text into a string to be displayed. @@ -283,7 +314,8 @@ function textToString(text: text): string { function markersRangesOfTree(tree: Tree): Map<string, Range>{ const ranges: Map<string, Range> = new Map(); const toRanges = (tree: Tree): void => { - if (tree.id) ranges.set(tree.id, tree); + if (!isNode(tree)) return; + ranges.set(tree.id, tree); for (const child of tree.children) toRanges(child); }; toRanges(tree); @@ -292,22 +324,15 @@ function markersRangesOfTree(tree: Tree): Map<string, Range>{ // Find the closest covering tagged node of a given position. Returns // undefined if there is not relevant covering node. -function coveringNode(tree: Tree, pos: number): Tree | undefined { +function coveringNode(tree: Tree, pos: number): Node | undefined { + if (isLeaf(tree)) return undefined; if (pos < tree.from || pos > tree.to) return undefined; - if (pos === tree.from) return tree; - const res = Utils.first(tree.children, (c) => coveringNode(c, pos)); - if (res) return res.id ? res : tree; + const child = Utils.first(tree.children, (c) => coveringNode(c, pos)); + if (child && isNode(child)) return child; if (tree.from <= pos && pos < tree.to) return tree; return undefined; } -// Find the subtree whose root as the given marker as id, or undefined if it -// does not exists in the tree. -function findMarker(tree: Tree, marker: Marker): Tree | undefined { - if (tree.id === marker) return tree; - return Utils.first(tree.children, (c) => findMarker(c, marker)); -} - // ----------------------------------------------------------------------------- @@ -333,7 +358,7 @@ function createTextField(): Field<text> { // This aspect computes the tree representing the currently displayed function's // code, represented by the <Text> field. -const Tree = createAspect({ t: Text }, ({ t }) => textToTree(t) ?? leaf(0, 0)); +const Tree = createAspect({ t: Text }, ({ t }) => textToTree(t) ?? empty); // This aspect computes the markers ranges of the currently displayed function's // tree, represented by the <Tree> aspect. @@ -420,10 +445,10 @@ const CodeDecorator = createCodeDecorator(); function createCodeDecorator(): Extension { const hoveredClass = Decoration.mark({ class: 'cm-hovered-code' }); const selectedClass = Decoration.mark({ class: 'cm-selected-code' }); - const deps = { tree: Tree, marker: Marker, hovered: Hovered }; - return createDecorator(deps, ({ tree, marker: m, hovered: h }) => { - const selected = m && findMarker(tree, m); - const hovered = h && findMarker(tree, h); + const deps = { ranges: Ranges, marker: Marker, hovered: Hovered }; + return createDecorator(deps, ({ ranges, marker: m, hovered: h }) => { + const selected = m && ranges.get(m); + const hovered = h && ranges.get(h); const range = selected && selectedClass.range(selected.from, selected.to); const add = hovered && [ hoveredClass.range(hovered.from, hovered.to) ]; const set = range ? RangeSet.of(range) : RangeSet.empty; @@ -447,11 +472,9 @@ function createDeadCodeDecorator(): Extension { const deps = { dead: Dead, ranges: Ranges }; return createDecorator(deps, ({ dead, ranges }) => { const range = (marker: string): Range | undefined => ranges.get(marker); - function isDef<A>(a: A | undefined): a is A { return a !== undefined; } - const getRanges = (xs: string[]): Range[] => xs.map(range).filter(isDef); - const unreachableRanges = getRanges(dead.unreachable); + const unreachableRanges = mapFilter(dead.unreachable, range); const unreachable = unreachableRanges.map(r => uClass.range(r.from, r.to)); - const nonTermRanges = getRanges(dead.nonTerminating); + const nonTermRanges = mapFilter(dead.nonTerminating, range); const nonTerm = nonTermRanges.map(r => tClass.range(r.from, r.to)); return RangeSet.of(unreachable.concat(nonTerm), true); }); @@ -475,16 +498,11 @@ const PropertiesStatuses = createField<Properties.statusData[]>([]); const PropertiesRanges = createPropertiesRange(); interface PropertyRange extends Properties.statusData { range: Range } function createPropertiesRange(): Aspect<PropertyRange[]> { - const deps = { statuses: PropertiesStatuses, ranges: Ranges }; - return createAspect(deps, ({ statuses, ranges }) => { - type R = PropertyRange | undefined; - const isDef = (r: R): r is PropertyRange => r !== undefined; - const fn = (p: Properties.statusData): R => { - const range = ranges.get(p.key); - return range && { ...p, range }; - }; - return statuses.map(fn).filter(isDef); - }); + const deps = { props: PropertiesStatuses, ranges: Ranges }; + return createAspect(deps, ({ props, ranges }) => mapFilter(props, (p) => { + const range = ranges.get(p.key); + return range && { ...p, range }; + })); } // This aspect computes the tag associated to each property. @@ -657,6 +675,62 @@ function createContextMenuHandler(): Extension { +// ----------------------------------------------------------------------------- +// Tainted lvalues +// ----------------------------------------------------------------------------- + +type Taints = Eva.LvalueTaints; +const TaintedLvalues = createField<Taints[] | undefined>(undefined); + +function textOfTaint(taint: Eva.taintStatus): string { + switch (taint) { + case 'not_computed': return 'The taint has not been computed'; + case 'error': return 'There was an error during the taint computation'; + case 'not_applicable': return 'No taint for this lvalue'; + case 'direct_taint': return 'This lvalue can be affected by an attacker'; + case 'indirect_taint': + return 'This lvalue depends on path conditions that can \ + be affected by an attacker'; + case 'not_tainted': return 'This lvalue is safe' + } + return ''; +} + +const TaintedLvaluesDecorator = createTaintedLvaluesDecorator(); +function createTaintedLvaluesDecorator(): Extension { + const mark = Decoration.mark({ class: 'cm-tainted' }); + const deps = { ranges: Ranges, tainted: TaintedLvalues }; + return createDecorator(deps, ({ ranges, tainted = [] }) => { + const find = (t: Taints): Range | undefined => ranges.get(t.lval); + const marks = mapFilter(tainted, find).map(r => mark.range(r.from, r.to)); + return RangeSet.of(marks, true); + }); +} + +const TaintTooltip = createTaintTooltip(); +function createTaintTooltip(): Extension { + const deps = { hovered: Hovered, ranges: Ranges, tainted: TaintedLvalues }; + return createTooltip(deps, ({ hovered, ranges, tainted }) => { + const hoveredTaint = tainted?.find(t => t.lval === hovered); + const hoveredNode = hovered && ranges.get(hovered); + if (!hoveredTaint || !hoveredNode) return undefined; + return { + pos: hoveredNode.from, + above: true, + strictSide: true, + arrow: true, + create: () => { + const dom = document.createElement('div'); + dom.className = 'cm-tainted-tooltip'; + dom.textContent = textOfTaint(hoveredTaint.taint); + return { dom }; + } + } + }); +} + + + // ----------------------------------------------------------------------------- // Code highlighting and parsing // ----------------------------------------------------------------------------- @@ -712,6 +786,13 @@ function useFctCallers(fct: Fct): Caller[] { return result.map(([fct, marker]) => ({ fct, marker })); } +// Server request handler returning the tainted lvalues. +function useFctTaints(fct: Fct): Eva.LvalueTaints[] { + const req = React.useMemo(() => Server.send(Eva.taintedLvalues, fct), [fct]); + const { result = [] } = Dome.usePromise(req); + return result; +} + // ----------------------------------------------------------------------------- @@ -727,6 +808,8 @@ const baseExtensions: Extension[] = [ ContextMenuHandler, PropertiesGutter, foldGutter(), + TaintedLvaluesDecorator, + TaintTooltip, Highlight, new LanguageSupport(cppLanguage), ]; @@ -773,21 +856,7 @@ function Editor(): JSX.Element { Hovered.set(editor.current, hovered?.marker); Dead.set(editor.current, useFctDead(fct)); Callers.set(editor.current, useFctCallers(fct)); - - /* - const getTaints = Eva.taintedLvalues; - const taintReq = React.useMemo(() => Server.send(getTaints, fct), [fct]); - const { result: taints } = Dome.usePromise(taintReq); - - if (taints && data) { - console.log('-----'); - for (const taint of taints) { - const range = data.ranges.get(taint.lval); - console.log (taint.lval, range, taint.before, taint.after); - } - console.log('-----'); - } - */ + TaintedLvalues.set(editor.current, useFctTaints(fct)); return <div className='cm-global-box' ref={parent} />; } diff --git a/ivette/src/sandbox/dark-code.css b/ivette/src/sandbox/dark-code.css index b6db04d0853..0f2c5b341f7 100644 --- a/ivette/src/sandbox/dark-code.css +++ b/ivette/src/sandbox/dark-code.css @@ -38,6 +38,28 @@ .cm-dead-code { color: var(--dead-code); } .cm-non-term-code { color: var(--dead-code); } +.cm-tainted { + text-decoration: underline var(--warning) solid 1px; + text-decoration-skip-ink: none; + text-underline-position: under; +} + +.cm-tooltip.cm-tainted-tooltip { + background-color: var(--background-softer); + color: var(--text-highlighted); + border: 1px solid var(--background-profound); + padding: 2px 7px; + border-radius: 4px; +} + +.cm-tooltip-above.cm-tainted-tooltip .cm-tooltip-arrow:before { + border-top-color: var(--background-profound); +} + +.cm-tooltip-above.cm-tainted-tooltip .cm-tooltip-arrow:after { + border-top-color: var(--background-softer); +} + .cm-def { color: var(--codemirror-def); } .cm-comment { color: var(--codemirror-comment); } diff --git a/src/plugins/eva/api/general_requests.ml b/src/plugins/eva/api/general_requests.ml index 59ea193b414..7525067edf4 100644 --- a/src/plugins/eva/api/general_requests.ml +++ b/src/plugins/eva/api/general_requests.ml @@ -69,6 +69,8 @@ let () = Request.register ~package ~input:(module Kernel_ast.Kf) ~output:(module Data.Jlist (CallSite)) callers + + (* ----- Functions ---------------------------------------------------------- *) module Functions = @@ -93,6 +95,8 @@ struct Analysis.register_computation_hook (fun _ -> f () )) end + + (* ----- Dead code: unreachable and non-terminating statements -------------- *) type dead_code = @@ -109,6 +113,7 @@ module DeadCode = struct let unreachable = Record.field record ~name:"unreachable" ~descr:(Markdown.plain "List of unreachable statements.") (module Data.Jlist (Kernel_ast.Marker)) + let non_terminating = Record.field record ~name:"nonTerminating" ~descr:(Markdown.plain "List of reachable but non terminating statements.") (module Data.Jlist (Kernel_ast.Marker)) @@ -159,7 +164,9 @@ let () = Request.register ~package ~output:(module DeadCode) dead_code -(* ----- Register Eva information ------------------------------------------- *) + + +(* ----- Register Eva values information ------------------------------------ *) let term_lval_to_lval tlval = try Logic_to_c.term_lval_to_lval tlval @@ -199,167 +206,79 @@ let () = ~enable:Analysis.is_computed print_value -let evaluate_taint expr request = - let (let+) = Option.bind in - let Results.{ data ; indirect } = Results.expr_dependencies expr request in - let+ data = Results.is_tainted data request |> Result.to_option in - let+ indirect = Results.is_tainted indirect request |> Result.to_option in - Some (data, indirect) - -let print_taint fmt marker = - let loc = Cil_datatype.Location.unknown in - let expr, stmt = - match marker with - | Printer_tag.PLval (_kf, Kstmt stmt, lval) -> - Cil.new_exp ~loc (Lval lval), stmt - | Printer_tag.PExp (_kf, Kstmt stmt, expr) -> expr, stmt - | PVDecl (_kf, Kstmt stmt, vi) -> - Cil.new_exp ~loc (Lval (Var vi, NoOffset)), stmt - | PTermLval (_kf, Kstmt stmt, _ip, tlval) -> - let lval = term_lval_to_lval tlval in - Cil.new_exp ~loc (Lval lval), stmt - | _ -> raise Not_found - in - let evaluate_taint request = - let deps = Results.expr_dependencies expr request in - Result.get_ok (Results.is_tainted deps.data request), - Result.get_ok (Results.is_tainted deps.indirect request) - in - let before = evaluate_taint Results.(before stmt) in - let after = evaluate_taint Results.(after stmt) in - let str_taint = function - | Results.Untainted -> "untainted" - | Direct -> "direct taint" - | Indirect -> "indirect taint" - in - let pretty fmt = let open Results in function - | taint, Untainted -> Format.fprintf fmt "%s" (str_taint taint) - | t1, t2 -> - Format.fprintf fmt - "%s to the value, %s %s to values used to compute lvalues addresses" - (str_taint t1) (if t1 = Untainted then "but" else "and") (str_taint t2) - in - if before = after - then Format.fprintf fmt "%a" pretty before - else Format.fprintf fmt "Before: %a@\nAfter: %a" pretty before pretty after -let () = - let enable () = Analysis.is_computed () && Taint_domain.Store.is_computed () in - let title = - "Taint status:\n\ - - Direct taint: data dependency from values provided by the attacker, \ - meaning that the attacker may be able to alter this value\n\ - - Indirect taint: the attacker cannot directly alter this value, but he \ - may be able to impact the path by which its value is computed.\n\ - - Untainted: cannot be modified by the attacker." - in - Server.Kernel_ast.Information.register - ~id:"eva.taint" ~label:"Taint" ~descr: "Taint status according to Eva" - ~title ~enable print_taint -let () = - Analysis.register_computation_hook - (fun _ -> Server.Kernel_ast.Information.update ()) +(* ----- Register Eva taints information ------------------------------------ *) +let expr_of_lval v = Cil.new_exp ~loc:Cil_datatype.Location.unknown (Lval v) +module EvaTaints = struct + open Results -module LvalueTaints = struct - module Lval = Kernel_ast.Lval - - type taints = { data: Results.taint ; indirect: Results.taint } - type lvalue_taints = { lval: Lval.t ; before: taints ; after: taints } - - module Taint: Data.S with type t = Results.taint = struct - type t = Results.taint - let tag strs = Package.Junion (List.map (fun s -> Package.Jtag s) strs) - let jtype = tag [ "direct" ; "indirect" ; "untainted" ] - let to_json = function - | Results.Direct -> Json.of_string "direct" - | Results.Indirect -> Json.of_string "indirect" - | Results.Untainted -> Json.of_string "untainted" - let of_json json = - match Yojson.Basic.Util.to_string json with - | "direct" -> Results.Direct - | "indirect" -> Results.Indirect - | "untainted" -> Results.Untainted - | _ -> Data.failure "Not a valid taint status" - end + let evaluate expr request = + let (let+) = Option.bind in + let { data ; indirect } = expr_dependencies expr request in + let+ data = is_tainted data request |> Result.to_option in + let+ indirect = is_tainted indirect request |> Result.to_option in + Some (data, indirect) - module Taints: Data.S with type t = taints = struct - type t = taints - let tag strs = Package.Jrecord (List.map (fun s -> (s, Taint.jtype)) strs) - let jtype = tag [ "data" ; "indirect" ] - let to_json { data ; indirect } = - let data = Taint.to_json data in - let indirect = Taint.to_json indirect in - `Assoc [ ("data", data) ; ("indirect", indirect) ] - let of_json = function - | `Assoc [ ("data", data) ; ("indirect", indirect) ] -> - { data = Taint.of_json data ; indirect = Taint.of_json indirect } - | _ -> Data.failure "Not a valid taints record" - end + let expr_of_marker = let open Printer_tag in function + | PLval (_, Kstmt stmt, lval) -> Some (expr_of_lval lval, stmt) + | PExp (_, Kstmt stmt, expr) -> Some (expr, stmt) + | PVDecl (_, Kstmt stmt, vi) -> Some (expr_of_lval (Var vi, NoOffset), stmt) + | PTermLval (_, Kstmt stmt, _, tlval) -> + Some (term_lval_to_lval tlval |> expr_of_lval, stmt) + | _ -> None - module LvalueTaints: Data.S with type t = lvalue_taints = struct - type t = lvalue_taints - let jtype = Package.Jrecord [ - ("lval", Lval.jtype) ; - ("before", Taints.jtype) ; - ("after", Taints.jtype) - ] - let to_json { lval ; before ; after } = - let lval = Lval.to_json lval in - let before = Taints.to_json before in - let after = Taints.to_json after in - `Assoc [ ("lval", lval) ; ("before", before) ; ("after", after) ] - let of_json = function - | `Assoc [ ("lval", lval) ; ("before", before) ; ("after", after) ] -> - let lval = Lval.of_json lval in - let before = Taints.of_json before in - let after = Taints.of_json after in - { lval ; before ; after } - | _ -> Data.failure "Not a valid lval taints" - end + let of_marker marker = + let (let+) = Option.bind in + let+ expr, stmt = expr_of_marker marker in + let+ before = evaluate expr (before stmt) in + let+ after = evaluate expr (after stmt) in + Some (before, after) - class tainted_lvalues taints = object (self) - inherit Visitor.generic_frama_c_visitor - (Visitor_behavior.copy (Project.current ())) - method! vlval lval = - match self#current_stmt with - | None -> DoChildren - | Some stmt -> - let expr = Cil.new_exp ~loc:Cil_datatype.Location.unknown (Lval lval) in - let before = evaluate_taint expr Results.(before stmt) in - let after = evaluate_taint expr Results.(after stmt) in - let add r = Cil_datatype.Lval.Hashtbl.add taints lval (stmt, r) in - let is_tainted (d, i) = Results.(d != Untainted || i != Untainted) in - let is_tainted b a = is_tainted b || is_tainted a in - match before, after with - | Some b, Some a when is_tainted b a -> add (b, a) ; DoChildren - | _, _ -> DoChildren - end + let to_string = function + | Untainted -> "untainted" + | Direct -> "direct taint" + | Indirect -> "indirect taint" - let to_value_taints lval (stmt, ((bdir, bind), (adir, aind))) ls = - let before = { data = bdir ; indirect = bind } in - let after = { data = adir ; indirect = aind } in - { lval = (Kstmt stmt, lval) ; before ; after } :: ls + let pretty fmt = function + | taint, Untainted -> Format.fprintf fmt "%s" (to_string taint) + | data, indirect -> + let sep = match data with Untainted -> "but" | _ -> "and" in + Format.fprintf fmt + "%s to the value, %s %s to values used to compute lvalues adresses" + (to_string data) sep (to_string indirect) + + let print_taint fmt marker = + let before, after = of_marker marker |> Option.get in + if before = after then Format.fprintf fmt "%a" pretty before + else Format.fprintf fmt "Before: %a@\nAfter: %a" pretty before pretty after + + let eva_taints_title = + "Taint status:\n\ + - Direct taint: data dependency from values provided by the attacker, \ + meaning that the attacker may be able to alter this value\n\ + - Indirect taint: the attacker cannot directly alter this value, but he \ + may be able to impact the path by which its value is computed.\n\ + - Untainted: cannot be modified by the attacker." - let tainted_lvals_of_func fundec = - let taints = Cil_datatype.Lval.Hashtbl.create 17 in - ignore (Visitor.visitFramacFunction (new tainted_lvalues taints) fundec) ; - Cil_datatype.Lval.Hashtbl.fold to_value_taints taints [] |> List.rev + let () = + let taint_computed = Taint_domain.Store.is_computed in + let enable () = Analysis.is_computed () && taint_computed () in + Server.Kernel_ast.Information.register + ~id:"eva.taint" ~label:"Taint" ~descr: "Taint status according to Eva" + ~title:eva_taints_title ~enable print_taint - let () = Request.register ~package ~kind:`GET ~name:"taintedLvalues" - ~descr:(Markdown.plain "Get the tainted lvalues of a given function") - ~input:(module (Kernel_ast.Fundec)) - ~output:(module (Data.Jlist (LvalueTaints))) - tainted_lvals_of_func + let update = Server.Kernel_ast.Information.update + let () = Analysis.register_computation_hook (fun _ -> update ()) end -(* ----- Red and tainted alarms --------------------------------------------- *) +(* ----- Taint statuses ----------------------------------------------------- *) -module Taint = struct +module TaintStatus = struct open Server.Data type taint = Results.taint = Direct | Indirect | Untainted @@ -374,49 +293,98 @@ module Taint = struct ~value dictionary let tag_not_computed = - tag (Error NotComputed) "not_computed" "" - "Not computed" "the Eva taint domain has not been enabled, \ - or the Eva analysis has not been run" + tag (Error NotComputed) "not_computed" "" "Not computed" + "the Eva taint domain has not been enabled, \ + or the Eva analysis has not been run" let tag_error = - tag (Error LogicError) "error" "Error" - "Error" "the memory zone on which this property depends \ - could not be computed" + tag (Error LogicError) "error" "Error" "Error" + "the memory zone on which this property depends could not be computed" let tag_not_applicable = - tag (Error Irrelevant) "not_applicable" "—" - "Not applicable" "no taint for this kind of property" + tag (Error Irrelevant) "not_applicable" "—" "Not applicable" + "no taint for this kind of property" let tag_direct_taint = - tag (Ok Direct) "direct_taint" "Tainted (direct)" - "Direct taint" + tag (Ok Direct) "direct_taint" "Tainted (direct)" "Direct taint" "this property is related to a memory location that can be affected \ by an attacker" let tag_indirect_taint = - tag (Ok Indirect) "indirect_taint" "Tainted (indirect)" - "Indirect taint" + tag (Ok Indirect) "indirect_taint" "Tainted (indirect)" "Indirect taint" "this property is related to a memory location whose assignment depends \ on path conditions that can be affected by an attacker" let tag_untainted = - tag (Ok Untainted) "not_tainted" "Untainted" - "Untainted property" "this property is safe" - - let () = Enum.set_lookup dictionary - begin function - | Error NotComputed -> tag_not_computed - | Error Irrelevant -> tag_not_applicable - | Error LogicError -> tag_error - | Ok Direct -> tag_direct_taint - | Ok Indirect -> tag_indirect_taint - | Ok Untainted -> tag_untainted - end + tag (Ok Untainted) "not_tainted" "Untainted" "Untainted property" + "this property is safe" + + let () = Enum.set_lookup dictionary @@ function + | Error NotComputed -> tag_not_computed + | Error Irrelevant -> tag_not_applicable + | Error LogicError -> tag_error + | Ok Direct -> tag_direct_taint + | Ok Indirect -> tag_indirect_taint + | Ok Untainted -> tag_untainted let data = Request.dictionary ~package ~name:"taintStatus" ~descr:(Markdown.plain "Taint status of logical properties") dictionary include (val data : S with type t = (taint, error) result) +end + + + +(* ----- Tainted lvalues ---------------------------------------------------- *) + +module LvalueTaints = struct + module Table = Cil_datatype.Lval.Hashtbl + + module Status = struct + type record + let record: record Data.Record.signature = Data.Record.signature () + let field name d = Data.Record.field record ~name ~descr:(Markdown.plain d) + let lval_field = field "lval" "tainted lvalue" (module Kernel_ast.Lval) + let taint_field = field "taint" "taint status" (module TaintStatus) + let name, descr = "LvalueTaints", Markdown.plain "Lvalue taint status" + let publication = Data.Record.publish record ~package ~name ~descr + include (val publication: Data.Record.S with type r = record) + let create lval taint = set lval_field lval @@ set taint_field taint default + end + + let current_project () = Visitor_behavior.copy (Project.current ()) + class tainted_lvalues taints = object (self) + inherit Visitor.generic_frama_c_visitor (current_project ()) + method! vlval lval = + let expr = expr_of_lval lval in + match self#current_stmt with + | None -> DoChildren + | Some stmt -> + match Results.after stmt |> EvaTaints.evaluate expr with + | Some (Results.Untainted, _) -> DoChildren + | Some (t, _) -> Table.add taints lval (Kstmt stmt, t) ; DoChildren + | None -> DoChildren + end + + let get_tainted_lvals fundec = + let taints = Table.create 17 in + Visitor.visitFramacFunction (new tainted_lvalues taints) fundec |> ignore ; + let fn lval (ki, taint) acc = Status.create (ki, lval) (Ok taint) :: acc in + Table.fold fn taints [] |> List.rev + + let () = Request.register ~package ~kind:`GET ~name:"taintedLvalues" + ~descr:(Markdown.plain "Get the tainted lvalues of a given function") + ~input:(module (Kernel_ast.Fundec)) + ~output:(module (Data.Jlist (Status))) + get_tainted_lvals +end + + + +(* ----- Red and tainted alarms --------------------------------------------- *) + +module PropertiesData = struct + open TaintStatus let zone_of_predicate kinstr predicate = let state = Results.(before_kinstr kinstr |> get_cvalue_model) in @@ -439,40 +407,39 @@ module Taint = struct | _ -> Error Irrelevant let is_tainted_property ip = - if not (Analysis.is_computed () && Taint_domain.Store.is_computed ()) - then Error NotComputed - else + if Analysis.is_computed () && Taint_domain.Store.is_computed () then let (let+) = Result.bind in let kinstr = Property.get_kinstr ip in let+ predicate = get_predicate ip in let+ zone = zone_of_predicate kinstr predicate in let result = Results.(before_kinstr kinstr |> is_tainted zone) in Result.map_error (fun _ -> NotComputed) result -end - -let model = States.model () - -let () = States.column model ~name:"priority" - ~descr:(Markdown.plain "Is the property invalid in some context \ - of the analysis?") - ~data:(module Data.Jbool) - ~get:(fun ip -> Red_statuses.is_red ip) + else Error NotComputed -let () = States.column model ~name:"taint" - ~descr:(Markdown.plain "Is the property tainted according to \ - the Eva taint domain?") - ~data:(module Taint) - ~get:(fun ip -> Taint.is_tainted_property ip) + let () = + let model = States.model () in + let descr = "Is the property invalid in some context of the analysis?" in + States.column model + ~name:"priority" + ~descr:(Markdown.plain descr) + ~data:(module Data.Jbool) + ~get:Red_statuses.is_red ; + let descr = "Is the property tainted according to the Eva taint domain?" in + States.column model + ~name:"taint" + ~descr:(Markdown.plain descr) + ~data:(module TaintStatus) + ~get:is_tainted_property ; + ignore @@ States.register_array + ~package + ~name:"properties" + ~descr:(Markdown.plain "Status of Registered Properties") + ~key:(fun ip -> Kernel_ast.Marker.create (PIP ip)) + ~keyType:Kernel_ast.Marker.jproperty + ~iter:Property_status.iter + model +end -let _array = - States.register_array - ~package - ~name:"properties" - ~descr:(Markdown.plain "Status of Registered Properties") - ~key:(fun ip -> Kernel_ast.Marker.create (PIP ip)) - ~keyType:Kernel_ast.Marker.jproperty - ~iter:Property_status.iter - model (* ----- Analysis statistics ------------------------------------------------ *) @@ -684,6 +651,7 @@ let _array = model + (* ----- Domains states ----------------------------------------------------- *) let compute_lval_deps request lval = -- GitLab