Beispiel #1
0
        def scan_syscall_end(self, inst, entry, regs, dataset, regset):
            """
            Scan registers to produce a syscall end node.
            """
            self.in_syscall = False

            # create a node for the syscall start
            if self.code.value == self.SyscallCode.SYS_MMAP:
                ret_reg = 3  # return in $c3
                origin = CheriNodeOrigin.SYS_MMAP
            else:
                # we do not do anything for other syscalls
                return None

            data = NodeData()
            data.cap = CheriCap(regs.cap_reg[ret_reg])
            data.cap.t_alloc = entry.cycles
            # XXX may want a way to store call pc and return pc
            data.pc = entry.pc
            data.origin = origin
            data.is_kernel = False
            node = dataset.add_vertex()
            dataset.vp.data[node] = data
            # attach the new node to the capability node in ret_reg
            # and replace it in the register set
            parent = regset[ret_reg]
            dataset.add_edge(parent, node)
            regset[ret_reg] = node
            return node
Beispiel #2
0
    def make_root_node(self, entry, cap, time=0, pc=None):
        """
        Create a root node of the provenance graph and add it to the dataset.

        :param entry: trace entry of the current instruction
        :type entry: `pycheritrace.trace_entry`
        :param cap: capability register value
        :type cap: :class:`pycheritrace.capability_register`
        :param time: optional allocation time
        :type time: int
        :param: pc: optional PC value for the root node
        :type pc: int
        :return: the newly created node
        :rtype: :class:`graph_tool.Vertex`
        """
        data = NodeData()
        data.cap = CheriCap(cap)
        # if pc is 0 indicate that we do not have a specific
        # instruction for this
        data.cap.t_alloc = time
        data.pc = entry.pc if pc is None else pc
        data.origin = CheriNodeOrigin.ROOT
        data.is_kernel = entry.is_kernel()

        # create graph vertex and assign the data to it
        vertex = self.dataset.add_vertex()
        self.dataset.vp.data[vertex] = data
        return vertex
Beispiel #3
0
 def _match_perm(self, inst, match):
     """Check if this instruction uses capabilities with the given perms"""
     if self.match_perm is None:
         return match
     match_perm = False
     for operand in inst.operands:
         if not operand.is_capability:
             continue
         if operand.value is None:
             # the register in the register set is not valid
             continue
         cap_reg = CheriCap(operand.value)
         match_perm = cap_reg.has_perm(self.match_perm)
         if match_perm:
             break
     return self._update_match_result(match, match_perm)
Beispiel #4
0
    def scan_clc(self, inst, entry, regs, last_regs, idx):
        """
        clc:
        Operand 0 is the register with the new node
        The parent is looked up in memory or a root node is created
        """
        if not self._do_scan(entry):
            return False

        cd = inst.op0.cap_index
        try:
            node = self.regset.memory_map[entry.memory_address]
        except KeyError:
            logger.debug("Load c%d from new location 0x%x", cd,
                         entry.memory_address)
            node = None

        # if the capability loaded from memory is valid, it
        # can be safely assumed that it corresponds to the node
        # stored in the memory_map for that location, if there is
        # one. If there is no node in the memory_map then a
        # new node can be created from the valid capability.
        # Otherwise something has changed the memory location so we
        # clear the memory_map and the regset entry.
        if not inst.op0.value.valid:
            self.regset[cd] = None
            if node is not None:
                del self.regset.memory_map[entry.memory_address]
        else:
            # check if the load instruction has committed
            old_cd = CheriCap(last_regs.cap_reg[cd])
            curr_cd = CheriCap(regs.cap_reg[cd])
            if old_cd != curr_cd:
                # the destination register was updated so the
                # instruction did commit

                if node is None:
                    # add a node as a root node because we have never
                    # seen the content of this register yet.
                    node = self.make_root_node(entry,
                                               inst.op0.value,
                                               time=entry.cycles)
                    logger.debug("Found %s value %s from memory load",
                                 inst.op0.name, self.dataset.vp.data[node])
                    self.regset.memory_map[entry.memory_address] = node
                self.regset[cd] = node
        return False
Beispiel #5
0
    def build_dataset(self):
        """
        Load the provenance graph and only retain SYS_* nodes with
        relevant alloc and free times.

        XXX This is a PoC, the actual transformation should be done
        on the provenance graph directly
        XXX Make a generic graph transformation module based on visitors
        """
        super(SyscallAddressMapPlot, self).build_dataset()
        logger.info("Filter syscall nodes and merge mmap/munmap")

        class _Visitor(gt.BFSVisitor):
            pass

        for node in self.dataset.vertices():
            data = self.dataset.vp.data[node]
            if data.origin == CheriNodeOrigin.SYS_MMAP:
                # look for munmap in the subtree, if none
                # is found the map survives until the process
                # exits
                syscall_node = self.syscall_graph.add_vertex()
                sys_node_data = NodeData()
                sys_node_data.cap = CheriCap()
                sys_node_data.cap.base = data.cap.base
                sys_node_data.cap.length = data.cap.length
                sys_node_data.cap.offset = data.cap.offset
                sys_node_data.cap.permissions = data.cap.permissions
                sys_node_data.cap.objtype = data.cap.objtype
                sys_node_data.cap.valid = data.cap.valid
                sys_node_data.cap.sealed = data.cap.sealed
                sys_node_data.cap.t_alloc = data.cap.t_alloc
                sys_node_data.origin = data.origin
                sys_node_data.pc = data.pc
                sys_node_data.is_kernel = data.is_kernel
                self.syscall_graph.vp.data[syscall_node] = sys_node_data

                _visitor = _Visitor()
                for descendant in gt.search.bfs_search(self.dataset, node,
                                                       _visitor):
                    descendant_data = self.dataset.vp.data[descendant]
                    if descendant_data.origin == CheriNodeOrigin.SYS_MUNMAP:
                        if sys_node_data.cap.t_free != -1:
                            logger.error(
                                "Multiple MUNMAP for a single mapped block")
                            raise RuntimeError(
                                "Multiple MUNMAP for a single mapped block")
                        sys_node_data.cap.t_free = descendant_data.cap.t_alloc
Beispiel #6
0
        def scan_syscall_start(self, inst, entry, regs, dataset, regset):
            """
            Scan a syscall instruction and detect the syscall type
            and arguments.
            """
            code = self._get_syscall_code(regs)
            try:
                self.code = self.SyscallCode(code)
            except ValueError:
                # we are not interested in this syscall
                return
            self.in_syscall = True
            self.pc_syscall = entry.pc
            self.t_syscall = entry.cycles
            self.pc_eret = entry.pc + 4

            # create a node at syscall start for those system calls for
            # which we care about the arguments
            if self.code.value == self.SyscallCode.SYS_MUNMAP:
                src_reg = 3  # argument in $c3
                origin = CheriNodeOrigin.SYS_MUNMAP
            else:
                # we do not do anything for other syscalls
                return None

            data = NodeData()
            data.cap = CheriCap(regs.cap_reg[src_reg])
            data.cap.t_alloc = entry.cycles
            # XXX may want a way to store call pc and return pc
            data.pc = entry.pc
            data.origin = origin
            data.is_kernel = False
            node = dataset.add_vertex()
            dataset.vp.data[node] = data
            # attach the new node to the capability node in src_reg
            # and replace it in the register set
            parent = regset[src_reg]
            dataset.add_edge(parent, node)
            regset[src_reg] = node
            return node
Beispiel #7
0
 def _set_initial_regset(self, inst, entry, regs):
     """
     Setup the registers after the first eret
     """
     self.regs_valid = True
     logger.debug("Scan initial register set cycle: %d", entry.cycles)
     for idx in range(0, 32):
         cap = regs.cap_reg[idx]
         valid = regs.valid_caps[idx]
         if valid:
             node = self.make_root_node(entry, cap, pc=0)
             self.regset[idx] = node
         else:
             logger.warning("c%d not in initial set", idx)
             if idx == 29:
                 node = self.make_root_node(entry, None, pc=0)
                 cap = CheriCap()
                 cap.base = 0
                 cap.offset = 0
                 cap.length = 0xffffffffffffffff
                 cap.permissions = 0xffff  # all XXX should we only have EXEC and few other?
                 cap.valid = True
                 # set the guessed capability value to the vertex data
                 # property
                 self.dataset.vp.data[node].cap = cap
                 self.regset[idx] = node
                 logger.warning("Guessing KCC %s",
                                self.dataset.vp.data[node])
             if idx == 30:
                 # guess the value of KDC and use this in the initial register set
                 node = self.make_root_node(entry, None, pc=0)
                 self.regset[idx] = node
                 cap = CheriCap()
                 cap.base = 0
                 cap.offset = 0
                 cap.length = 0xffffffffffffffff
                 # cap.permissions = (
                 #     CheriCapPerm.LOAD | CheriCapPerm.STORE |
                 #     CheriCapPerm.EXEC | CheriCapPerm.GLOBAL |
                 #     CheriCapPerm.CAP_LOAD | CheriCapPerm.CAP_STORE |
                 #     CheriCapPerm.CAP_STORE_LOCAL | CheriCapPerm.SEAL |
                 #     CheriCapPerm.SYSTEM_REGISTERS)
                 cap.permissions = 0xffff  # all
                 cap.valid = True
                 self.dataset.vp.data[node].cap = cap
                 self.regset[idx] = node
                 logger.warning("Guessing KDC %s",
                                self.dataset.vp.data[node])
Beispiel #8
0
    def scan_all(self, inst, entry, regs, last_regs, idx):

        # read entry from
        txt_inst = self._next_txt_instr()
        logger.debug("Scan txt:<%s>, bin:%s", self._dump_txt_inst(txt_inst),
                     inst)
        try:
            # check that the instruction matches
            assert txt_inst["pc"] == entry.pc
            if self.pc_only:
                # only check pc, skip everything else
                return False
            if inst.opcode in ["mfc0"]:
                # these have weird behaviour so just ignore for now
                return False

            if txt_inst["opcode"] != inst.opcode:
                # opcode check is not mandatory due to disassembly differences
                # issue a warning anyway for now
                logger.warning("Opcode differ {%d} txt:<%s> bin:%s",
                               entry.cycles, self._dump_txt_inst(txt_inst),
                               inst)
            if "load" in txt_inst:
                assert txt_inst["load"] == entry.memory_address
            if "store" in txt_inst:
                assert txt_inst["store"] == entry.memory_address
            if "data" in txt_inst:
                if inst.opcode not in ["mfc0"]:
                    reg_number = entry.gpr_number()
                    for op in inst.operands:
                        if op.is_register and op.gpr_index == reg_number:
                            logger.debug("gpr:%d reg:%d")
                            assert txt_inst["data"] == op.value, \
                                "reg data do not match %d != %d" % (
                                    txt_inst["data"], op.value)
                            break
                #     # XXX we have a problem with extracting the jump target
                #     # from jal/j the binary trace have an offset that does
                #     # not make much sense..
                #     assert txt_inst["data"] == inst.op0.value
            if "cap" in txt_inst:
                cap = CheriCap(inst.op0.value)
                txt_cap = txt_inst["cap"]
                assert txt_cap["valid"] == cap.valid, \
                    "tag do not match %d != %d" % (
                        txt_cap["valid"], cap.valid)
                assert txt_cap["sealed"] == cap.sealed, \
                    "seal do not match %d != %d" % (
                        txt_cap["sealed"], cap.sealed)
                assert txt_cap["base"] == cap.base, \
                    "base do not match %x != %x" % (
                        txt_cap["base"], cap.base)
                assert txt_cap["length"] == cap.length, \
                    "length do not match %x != %x" % (
                        txt_cap["length"], cap.length)
                assert txt_cap["offset"] == cap.offset, \
                    "offset do not match %x != %x" % (
                        txt_cap["offset"], cap.offset)
                assert txt_cap["perms"] == cap.permissions, \
                    "perms do not match %x != %x" % (
                        txt_cap["perms"], cap.permissions)
                assert txt_cap["otype"] == cap.objtype, \
                    "otype do not match %x != %x" % (
                        txt_cap["otype"], cap.objtype)

        except AssertionError:
            logger.error("Assertion failed at {%d} inst:%s txt:<%s>",
                         entry.cycles, inst, self._dump_txt_inst(txt_inst))
            raise
        self.progress.advance()
        return False
Beispiel #9
0
 def dump_cap(self, cap):
     chericap = CheriCap(cap)
     return str(chericap)