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
//! Analyse Isolated Return Sites.
//!
//! Taint that reaches a return site implies that there is some path from the
//! call to a fallible function to the return instruction where the return value
//! is not checked. If additionally, the taint is not returned to the caller
//! we have a bug, since with the return of the function all information about
//! success or failure of the function call is lost.
//!
//! For each function that is called at least once, we catch those cases during
//! the FP computation. However, for functions that are never called, e.g.,
//! exported functions in shared libraries or functions that are only ever
//! called indirectly, we need an additional pass once we have the result of
//! the FP computation.

use crate::abstract_domain::AbstractDomain;
use crate::analysis::graph::{Graph, Node, NodeIndex};
use crate::intermediate_representation::{Jmp, Project, Tid};
use crate::utils::log::CweWarning;

use std::collections::HashSet;
use std::sync::Arc;

use super::context;
use super::{generate_cwe_warning, MustUseCall};

/// Represents a return site of a function.
#[derive(Hash, Eq, PartialEq, Debug)]
pub struct ReturnSite<'a> {
    /// CFG node of the end of the block containing the return instruction.
    ret_node: NodeIndex,
    /// Calling convention of the function that returns.
    calling_convention: &'a Option<String>,
    /// Identifier of the return instruction.
    ret_insn_tid: &'a Tid,
}

impl<'a> ReturnSite<'a> {
    /// Create a new `ReturnSite`.
    fn new(
        ret_node: NodeIndex,
        calling_convention: &'a Option<String>,
        ret_insn_tid: &'a Tid,
    ) -> Self {
        Self {
            ret_node,
            calling_convention,
            ret_insn_tid,
        }
    }
}

/// The set of all isolated return sites of a binary.
pub type IsolatedReturns<'a> = HashSet<ReturnSite<'a>>;

/// Represents the post-fixpoint-computation pass that checks isolated return
/// sites for taint.
pub struct IsolatedReturnAnalysis<'a> {
    call: MustUseCall<'a>,
    isolated_returns: Arc<IsolatedReturns<'a>>,
    project: &'a Project,
    cwe_sender: crossbeam_channel::Sender<CweWarning>,
}

impl<'a> IsolatedReturnAnalysis<'a> {
    /// Create a new `IsolatedReturnAnalysis`.
    pub fn new(
        call: MustUseCall<'a>,
        isolated_returns: Arc<IsolatedReturns<'a>>,
        project: &'a Project,
        cwe_sender: crossbeam_channel::Sender<CweWarning>,
    ) -> Self {
        Self {
            call,
            isolated_returns,
            project,
            cwe_sender,
        }
    }

    /// Checks isolated return sites for taint with the results of the given
    /// `computation`.
    ///
    /// Generates CWE warnings when non-returned taint is found. We have no
    /// caller context, i.e., no aID renaming map, so we can not tell if memory
    /// taint is returned or not. Currently we always assume that memory taint
    /// will *not* be reachable for the caller (generates FPs).
    pub fn analyze(&self, computation: &context::FpComputation<'_, '_>) {
        for (taint_state, calling_convention, ret_insn_tid) in
            // Filter isolated returns with a taint state.
            self.isolated_returns.iter().filter_map(
                    |ReturnSite {
                         ret_node,
                         calling_convention,
                         ret_insn_tid,
                     }| {
                        computation
                            .node_values()
                            .get(ret_node)
                            .map(|state| (state.unwrap_value(), calling_convention, ret_insn_tid))
                    },
                )
        {
            if !taint_state.has_register_taint() {
                // Emit a warning since we do not consider cases where memory
                // taint may be returned.
                generate_cwe_warning(
                    &self.cwe_sender,
                    &self.call,
                    ret_insn_tid,
                    "isolated_returns_no_reg_taint",
                );
            } else if let Some(calling_convention) = self
                .project
                .get_specific_calling_convention(calling_convention)
            {
                // If no taint is returned we emit a warning.
                if calling_convention
                    .get_all_return_register()
                    .iter()
                    .all(|return_register| taint_state.get_register_taint(return_register).is_top())
                {
                    generate_cwe_warning(
                        &self.cwe_sender,
                        &self.call,
                        ret_insn_tid,
                        "isolated_returns_no_reg_taint_returned",
                    );
                }
            }
        }
    }
}

/// Get the set of all isolated return sites in the given interprocedural CFG.
pub fn get_isolated_returns<'a>(cfg: &Graph<'a>) -> IsolatedReturns<'a> {
    cfg.node_indices()
        .filter_map(|node| {
            if cfg.edges(node).next().is_some() {
                // By definition, a node with outgoing edges cannot be an
                // isolated return.
                None
            } else if let Node::BlkEnd(blk, sub) = cfg[node] {
                blk.term.jmps.iter().find_map(|jmp| {
                    if matches!(jmp.term, Jmp::Return(_)) {
                        Some(ReturnSite::new(
                            node,
                            &sub.term.calling_convention,
                            &jmp.tid,
                        ))
                    } else {
                        None
                    }
                })
            } else {
                None
            }
        })
        .collect()
}