def cmd_repair(fd, args): """repairing attacks: hint attacker""" if len(args) < 1: cmd_help(fd) return hint = args[0] pick = "pick_attack_execve" if len(args) < 2 else args[1] name = attacker.find_attack_node(logd, pick, hint) attack = mgrapi.RegisteredObject.by_name_load(name) if attack is None: dbg.error('missing attack node:', name) return chkpt = max(c for c in attack.checkpoints if c < min(attack.actions)) assert chkpt assert len(attack.actions) == 1 for a in attack.actions: dbg.info("cancel: %s" % a.argsnode) a.argsnode.data = None a.cancel = True dbg.info("pick:", chkpt) The(CtrlFSM).set_state("repairing") ctrl.repair(attack, chkpt) The(CtrlFSM).set_state("init") rtn_cmd(fd, "done")
def explore_path(path, match): m = [] for l in path.effects: if isinstance(e, PathCall): if e.func == match.func: m.append(e) # error: if more than single instance if len(m) != 1: dbg.error("! error (L=%d): %s", len(m), str(path)) for l in m: dbg.error("! NOTE: %s", str(l))
def main(): atexit.register(retroctl.disable) parser = optparse.OptionParser(usage="%prog [options] LOG-DIRECTORY") parser.add_option("-d", "--dry", default=False, action="store_true", dest="dryrun", help="dry run for repair") parser.add_option("-p", "--profile", default=False, action="store_true", dest="profile", help="profiling execution time") (opts, args) = parser.parse_args() if opts.profile: runopts.set_profile() if len(args) != 2: parser.print_usage() exit(1) osloader1 = osloader.OsLoader(args[0]) # Ensure that all syscalls have been loaded, hence connected osloader1.all() nodes1 = mgrapi.RegisteredObject.all() # Only load second executions's objects after we've safely stowed the # relevant part of the first's graph osloader2 = osloader.OsLoader(args[1]) d = {} filenodes = [ node for node in nodes1 if isinstance(node, fsmgr.FileDataNode) ] for node in filenodes: try: chkpt = min(node.checkpoints) except AttributeError: dbg.error('skipping rollback due to lack of checkpoints') continue else: assert chkpt d[node] = chkpt runopts.set_dryrun(opts.dryrun) ctrl.repair2(d)
def _read_records(self, offlist): records = [] for (fidx, offset) in sorted(offlist): f = self.sfs[fidx] f.seek(0, 2) flen = f.tell() if offset >= flen: dbg.error('XXX system call offset out of range', offset, flen, f.name) continue f.seek(offset) try: r = read_syscall(f) records.append(r) except EOFError: ## Unfortunate, but ... pass return records
def redo(self): if self.argsnode.data is None: dbg.syscall("no argsnode data for %s, returning prematurely" % self) return if (hasattr(self.argsnode, "rerun") and self.argsnode.rerun.is_active()): rerun = self.argsnode.rerun pid = self.argsnode.data.pid expanding = not hasattr( self, 'parent_retnode') or not self.parent_retnode if expanding: child_pid = None else: child_pid = self.parent_retnode.origdata.ret (r, p) = rerun.clone(pid, child_pid) r.sid = self.argsnode.data.sid # TODO(ipopov): refactor into __fix_sid() if expanding: # This creates parent_retnode and child_retnode self.osloader.parse_record(r) else: self.child_retnode.data = deepcopy(self.child_retnode.origdata) self.parent_retnode.data = deepcopy( self.parent_retnode.origdata) # TODO(ipopov): should we change the retvalue here? (probably # not) # find child actor and setup for rerun r = self.parent_retnode.data child = ProcessActor.get(r.ret, r) child.rerun = rerun rerun.add(child) else: # This should not happen. dbg.error('no redo performed for clonesyscall action!')
def register(self): if self.retnode: self.outputset.add(self.retnode) if self.argsnode: self.inputset.add(self.argsnode) # register() should work even with only one retnode/argsnode dbg.connect("%s => connect(): (%s,%s)" % (self, self.argsnode, self.retnode)) # WTF???!!! This can (and does) put the return value of the system # call into the node for the argument node! :( # XXXX # Why is this necessary??? # XXXX # merge a bit for compatibility r = None if self.argsnode and self.argsnode.data: r = self.argsnode.data else: if self.retnode and self.retnode.data: r = self.retnode.data if r and self.retnode and self.retnode.data: setattr(r, "ret", self.retnode.data.ret) if r is None: return (rs, ws) = nrdep.nrdep(r) for x in rs | ws: objs = [] if type(x) == sysarg._inode: if x.prefix in ('file', 'link', 'dir'): n = fsmgr.FileDataNode.get(x.dev, x.ino) # add checkpoints of unlink syscall if r.name in ["unlinkat", "unlink", "rmdir"]: # find inodes in the .inodes self.__search_checkpoints(n, x) objs.append(n) elif x.prefix == 'socket': n = sockmgr.SocketNode.get(x) objs.append(n) if n.sport and n.dip and n.dport: objs.append(networkmgr.NetworkNode.get(x)) # elif x.prefix in ['pts', 'ptm']: # objs.append(ptymgr.PtyNode.get(os.major(x.rdev), 0)) # elif x.prefix == 'fifo': # objs.append(fifomgr.FifoNode.get(x)) # elif x.prefix == 'cdev': # objs.append(cdevmgr.CdevNode.get(x)) else: dbg.error('need data node for inode type', x.prefix) elif type(x) == sysarg.process: dbg.info('pid:%s -> pid:%s' % (r.pid, x)) elif type(x) == sysarg.dentry: objs.append( fsmgr.DirentDataNode.get(x.inode.dev, x.inode.ino, x.name)) else: dbg.error('need data node for', x, 'type', type(x)) if len(objs) != 0: for n in objs: if x in rs: self.inputset.add(n) if x in ws: self.outputset.add(n)
def redo(self): dbg.syscall("redoing") if self.argsnode.data is None: dbg.syscall("no argsnode data for %s, returning prematurely" % self) return # share expanding & following if (hasattr(self.argsnode, "rerun") and self.argsnode.rerun.is_active()): rerun = self.argsnode.rerun pid = self.argsnode.data.pid # wait syscalls: make it non-blocking if self.argsnode.data.name == "wait4": result = rerun.wait4(pid) if not result: self.flatten() raise mgrapi.ReschedulePendingException else: (r, p) = result elif self.argsnode.data.name == "exit_group": (r, p) = rerun.exit_group(pid) # from Taesoo: # while expanding (after canceling all nodes), you should # create new node (pcall) to dynamically create new nodes of # parents # # The current process is exiting. But, perhaps its reexecution # has spawned new children that must persist. if rerun.is_expanding() and len(rerun.pids) >= 1: for childpid in rerun.pids: dbg.info("reviewing: %s" % childpid) (childr, childp) = rerun.next(True, childpid) assert childr.usage & ENTER self.osloader.parse_record(childr) return else: (r, p) = rerun.next(False, pid) r.sid = self.argsnode.data.sid # Is this actually true?: # Vestigial, from Taesoo: r is None with exit_group()-like # syscalls assert not r or (r and r.usage & EXIT) if r: # self.retnode exists iff the exit record has already been # parsed for the syscall in question, i.e. iff this syscall # has already been carried out, i.e. iff we are retracing it. if self.retnode: # following self.retnode.data = deepcopy(self.retnode.origdata) self.retnode.data.ret = r.ret else: # exploring new nodes/edges self.osloader.parse_record(r) self.retnode.rerun = rerun return else: r = self.argsnode.data func = getattr(self, "syscall_%s" % r.name, None) if func: # exit_group() has no retnode if self.retnode: self.retnode.data = func(self.argsnode, self.retnode) return # raise Exception('not impl') dbg.error("Not implemented:", r.name)