-
Notifications
You must be signed in to change notification settings - Fork 4
Plugins
ExpBridge provides a comprehensive interface for reproducing upstream kernel bugs. It can easily integrate with existing bug assessment tools, as a form of a standalone plugin of ExpBridge.
In fact, all the current functionalities of ExpBridge (reproducing bugs on downstream, extracting kernel trace, automatically tuning the original PoCs, etc.) were implemented as one or more plugins. Plugins can share data with each other and collaborate with other plugins. ExpBridge also provides rich API for common functionalities, such as launching VM, detecting kernel crashes, etc.
A blank plugin the following entries:
-
prepare()
is a common entry to prepare a plugin. It has no arguments, users are able to pass customized arguments through ExpBridge configuration file.prepare()
function can pass arguments toprepare_on_demand()
if necessary. -
prepare_on_demand()
is used to setup variables and conditions. It's also handy when you are testing a plugin by passing the arguments directly to the plugin without a config file. -
success()
can only be called once for each case. It makes the current case move to thesucceed
category. -
run()
is the main entry of a plugin. It's where you implement the main functionality of your plugin. It returnsTrue
to create a success stamp, otherwise, no stamp will be created. -
generate_report()
executes afterrun()
returnsTrue
. -
cleanup()
executes after plugin is finished.
from plugins import AnalysisModule
class Template(AnalysisModule):
NAME = "Template"
REPORT_START = "======================Template Report======================"
REPORT_END = "==================================================================="
REPORT_NAME = "Report_Template"
DEPENDENCY_PLUGINS = []
def __init__(self):
super().__init__()
def prepare(self):
plugin = self.cfg.get_plugin(self.NAME)
if plugin == None:
self.err_msg("No such plugin {}".format(self.NAME))
try:
self.greeting = int(plugin.greeting)
except AttributeError:
self.err_msg("Failed to get greeting")
return False
return self.prepare_on_demand()
def prepare_on_demand(self):
self._prepared = True
return True
def success(self):
return self._move_to_success
def run(self):
"""
do something
True: plugin return successfully
False: something goes wrong, stamp will not be created
"""
self.logger.info("Hello you, {}".format(self.greeting))
return True
def generate_report(self):
final_report = "\n".join(self.report)
self.info_msg(final_report)
self._write_to(final_report, self.REPORT_NAME)
def _write_to(self, content, name):
file_path = "{}/{}".format(self.path_case_plugin, name)
super()._write_to(content, file_path)
def cleanup(self):
super().cleanup()
-
name
is the string of plugin name -
analyzor
is the instance of current plugin -
init()
passesmanager
to the plugin. Themanager
contains all necessary info of the current case, including case hash, config, logger, case path and etc.init()
is called automatically by the deployer whenever a plugin is loaded. -
setup()
sets the plugin path and creates the plugin work folder in the case directory. It also prepares the plugin logger. -
install_analyzor()
installs the next plugin. It's automatically called by the deployer. -
run()
eventually executes the plugin'srun()
function. Once the plugin has finished running, it dumps the results to the plugin folder, and procceed the cleanup process. -
prepare()
checks whether the dependencies has met. If it does, procceed theprepare()
of the plugin. -
generate_report()
generates the plugin report in the plugin folder. -
success()
calls the plugin'ssuccess()
. -
create_stamp()
generates the finish stamp for the plugin. -
check_stamp()
checks whether the plugin has a finish stamp -
null_results()
resets the finish and results of the plugin -
plugin_finished()
returns whether a plugin has finished successfully. -
plugin_capable()
returns whether a plugin is enabled. -
pass
-
dump_results()
generates aresults.json
in the plugin folder. -
build_mainline_kernel(self, commit=None, config=None, image=None, gcc_version=None, kernel=None, patch="", keep_ori_config=False, extra_cmd="", kernel_repo="", branch="")
build_mainline_kernel()
controls the building process of a upstream kernel.commit
specifies the git commit of the kernel source. Ifcommit
isNone
, it uses the case kernel commit.config
links to a url of a kernel config that is used for compilation. Ifconfig
isNone
, it uses the case kernel config.image
specfies the image name, e.g., stretch.gcc_version
is decided by case report date unless if it'sNone
.kernel
can be upstream, net, linux-next and etc. It will be set by the case if it's not specified.patch
points to a path of a kernel patch, it will be apply to the source code if it's a valid patch. Ifkeep_ori_config
isTrue
, the kernel config will not be changed.extra_cmd
will be executed before compiling the kernel.kernel_repo
is a url to a Linux kernel repo if it's need to be specified.branch
indicates a particular branch of Linux kernel source code. -
err_msg()
logs an error message. -
info_msg()
logs a info message -
debug_msg
logs a debug message
-
ssh_port
specifies the ssh port of the reproducer VM -
mon_port
specifies the qemu monitor port of the reproducer VM -
gdb_port
specifies the gdb port of the reproducer VM -
prepare()
creates an image snapshot of the testing distro. -
create_snapshot()
creates an image snapshot of the testing distro.src
specifies the testing distro image.img_dir
is the path of the directory that keep the snapshot image.image_name
indicates the snapshot image name.target_format
indicates the format of the snapshot image. It needs to be set toraw
when creating snapshot of bullseye and android image. -
save_crash_log()
save the content oflog_msg
to{self.path_case}/crash_log-{name}
-
need_repro()
checks whether a case need to reproduce on the testing distro. A case will not reproduce when ExpBridge decides this bug doesn't affect the testing distro or the patch already exist in the testing distro kernel. -
reproduce(self, func, func_args, work_dir, timeout, vm_tag="reproducer", root=True, attempt=3, **kwargs)
reproduce()
is the main function for bug reproducing. It invokeslaunch_qemu()
to boot up the kernel and wait for the callback functionfunc
to execute.func_args
pass the arguments tofunc
.work_dir
specifies the folder that store the QEMU log and launch script.timeout
indicates the maximum time for running the QEMU.vm_tag
is used as a identifier for QEMU log. PoC will run as root user ifroot
isTrue
.attempt
indicates how many times that ExpBridge tries to reproduce the bug if it fails.**kwargs
contains arguments forVM
class, they can be specified as well. -
launch_qemu(self, c_hash=0, log_suffix="", log_name=None, timeout=None, enable_gdb=False, enable_qemu_mon=False, gdb_port=None, mon_port=None, ssh_port=None, **kwargs)
launch_qemu()
returns a VM class.c_hash
represents that hash value of the bug, it usually comes with the bug from syzbot.log_suffix
append to the log file for better identification.log_name
specifies the name of the log file.timeout
indicates the maximum time for running the QEMU.enable_gdb
controls whether open the gdb port.enable_qemu_mon
controls whether open the QEMU monitor port.gdb_port
ormon_port
will be ignored they are not enabled.ssh_port
specifies the ssh port, it will be automatically set an unused port if it's not specified.**kwargs
contains arguments forVM
class, they can be specified as well. -
run_qemu()
boots up the QEMU and invoke the callback functionfunc
.*args
is the arguments offunc
-
__init__(self, linux :str, kernel :Vendor(), port, image, hash_tag, key, vmlinux=None, tag='', arch='amd64', work_path='/tmp/', mem="4G", cpu="2", gdb_port=-1, mon_port=-1, timeout=None, debug=False, logger=None, log_name='vm.log', log_suffix="", snapshot=True)
linux
is the path to the upstream Linux directory. (Only for upstream)kernel
is theVendor()
class initiated from ExpBridge config.port
indicates the ssh port.image
is the path of the kernel image.hash_tag
indicates the case hahs from syzbot.key
is the path of the ssh key.vmlinux
is the path of vmlinux (Only for upstream).tag
is used for better identification.arch
indicates the architecture of the kernel.work_path
stores logs and launch scripts of QEMU.mem
indicates the how many memory is used by the VM.cpu
indicates how many cpu cores are used by the VM.gdb_port
indicates the gdb port.mon_port
indicates the QEMU monitor port.timeout
is maximum running time of the QEMU.debug
enables the debug mode.logger
specifies the logger instance.log_name
indicates the name of the log file.log_suffix
add additional suffix to the log file name.snapshot
indicates whether using snapshot for booting the image. -
reset()
resets QEMU instance by empty multipleVM()
flag, includingqemu_ready
,kill_qemu
,trigger_crash
, and etc. -
run()
boots up the kernel in QEMU and prepares monitor, logger, and timer for theVM()
instance.alternative_func
specifies the callback function that will be called after booting up.alternative_func_output
is aQueue
that all the output writes to.args
contains the arguments of thealternative_func
. -
wait()
function waits until thealternative_func
exits. It blocks the process. -
shutdown
runs the commandshutdown -h now
to shutdown theVM()
instance. -
kill_vm
force the theVM()
instance exits by killing all the related processes. -
upload
relies on scp to tansfer file to QEMU.user
indicates the scp user.src
is a list of files on the local machine that are ready to upload.dst
is the path in the virtual machine.wait
determines whether block the process. -
download
uses scp to transfer file from the QEMU to local machine.user
indicates the scp user.src
is a list of files on the virtual machine that are ready to upload.dst
is the path in the local machine.wait
determines whether block the process. -
command
uses ssh to execute command in the virtual machine.cmds
is the command.user
indicates the ssh user.wait
determines whether block the process.timeout
indicates the timeout for the ssh. -
is_qemu_ready()
determines the status of the QEMU by checking the ssh connect.