├── tools ├── model_manager │ ├── api │ │ ├── __init__.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── admin.py │ │ ├── tests.py │ │ ├── apps.py │ │ ├── lang_model │ │ │ ├── train.py │ │ │ ├── utils.py │ │ │ ├── dataset.py │ │ │ └── model.py │ │ └── views.py │ ├── model_manager │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── wsgi.py │ │ ├── urls.py │ │ └── settings.py │ ├── db.sqlite3 │ └── manage.py ├── minimize │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── mutate_prog │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── inspect_target │ └── Cargo.toml └── gen_prog │ ├── Cargo.toml │ └── src │ └── main.rs ├── rustfmt.toml ├── .gitignore ├── healer_core ├── src │ ├── value_pool.rs │ ├── lib.rs │ ├── mutation │ │ ├── res.rs │ │ ├── ptr.rs │ │ ├── group.rs │ │ ├── int.rs │ │ └── seq.rs │ ├── gen │ │ ├── group.rs │ │ ├── ptr.rs │ │ ├── res.rs │ │ ├── buffer.rs │ │ ├── int.rs │ │ └── mod.rs │ ├── verbose.rs │ ├── lang_mod │ │ ├── utils.rs │ │ ├── mod.rs │ │ ├── mutate.rs │ │ └── model.rs │ ├── parse │ │ └── prog_syntax.pest │ ├── scheduler.rs │ ├── ty │ │ ├── res.rs │ │ ├── ptr.rs │ │ └── common.rs │ ├── corpus.rs │ ├── syscall.rs │ ├── select.rs │ ├── context.rs │ └── relation.rs └── Cargo.toml ├── healer_utils ├── healer_vm │ ├── src │ │ ├── lib.rs │ │ └── ssh.rs │ └── Cargo.toml ├── healer_io │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── thread.rs │ │ └── tokio.rs └── healer_alloc │ ├── Cargo.toml │ └── src │ └── lib.rs ├── syz_wrapper ├── src │ ├── lib.rs │ ├── exec │ │ ├── message.rs │ │ ├── util.rs │ │ └── features.rs │ ├── repro.rs │ └── report.rs ├── patches │ ├── .clang-format │ ├── targets.diff │ ├── executor.diff │ ├── unix_sock_setup.h │ ├── sysgen.diff │ ├── ivshm_setup.h │ └── features.h ├── Cargo.toml └── benches │ ├── gen.rs │ └── mutate.rs ├── healer_fuzzer ├── src │ ├── util.rs │ ├── arch.rs │ ├── fuzzer_log.rs │ ├── feedback.rs │ ├── stats.rs │ ├── main.rs │ └── crash.rs └── Cargo.toml ├── Cargo.toml └── README.md /tools/model_manager/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/model_manager/api/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/model_manager/model_manager/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | edition = "2018" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | Cargo.lock 4 | **/*.rs.bk 5 | *.pdb 6 | /.idea -------------------------------------------------------------------------------- /tools/model_manager/api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /tools/model_manager/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /tools/model_manager/api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /tools/model_manager/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0ck1ng/mock/HEAD/tools/model_manager/db.sqlite3 -------------------------------------------------------------------------------- /healer_core/src/value_pool.rs: -------------------------------------------------------------------------------- 1 | //! Interesting value pool extracted from the progs in the corpus. 2 | 3 | /// TODO 4 | pub struct ValuePool; 5 | -------------------------------------------------------------------------------- /healer_utils/healer_vm/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod qemu; 2 | pub mod ssh; 3 | 4 | pub type HashMap = ahash::AHashMap; 5 | pub type HashSet = ahash::AHashSet; 6 | -------------------------------------------------------------------------------- /syz_wrapper/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod exec; 2 | pub mod report; 3 | pub mod repro; 4 | pub mod sys; 5 | 6 | type HashMap = ahash::AHashMap; 7 | type HashSet = ahash::AHashSet; 8 | -------------------------------------------------------------------------------- /tools/model_manager/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'api' 7 | -------------------------------------------------------------------------------- /healer_fuzzer/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | 3 | static STOP_SOON: AtomicBool = AtomicBool::new(false); 4 | 5 | pub fn stop_soon() -> bool { 6 | STOP_SOON.load(Ordering::Relaxed) 7 | } 8 | 9 | pub fn stop_req() { 10 | STOP_SOON.store(true, Ordering::Relaxed) 11 | } 12 | -------------------------------------------------------------------------------- /healer_utils/healer_vm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "healer_vm" 3 | description = "Healer VM manager" 4 | authors = ["Hao Sun "] 5 | version = "0.1.0" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | nix = "0.22" 10 | ahash = "0.7" 11 | thiserror = "1.0" 12 | log = "0.4" 13 | healer_io = {path = "../healer_io"} 14 | rand = {version = "0.8"} 15 | -------------------------------------------------------------------------------- /tools/minimize/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minimize" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | healer_core = {path = "../../healer_core"} 8 | syz_wrapper = {path = "../../syz_wrapper"} 9 | healer_alloc = {path = "../../healer_utils/healer_alloc"} 10 | rand = {version = "0.8", features = ["small_rng"]} 11 | env_logger = "0.9" 12 | log = "0.4" 13 | structopt = "0.3" -------------------------------------------------------------------------------- /healer_utils/healer_io/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "healer_io" 3 | description = "Healer IO utility" 4 | authors = ["Hao Sun "] 5 | version = "0.1.0" 6 | edition = "2018" 7 | 8 | [features] 9 | tokio-io = ["tokio"] 10 | 11 | [dependencies] 12 | log = "0.4" 13 | 14 | [dependencies.tokio] 15 | version = "1" 16 | features = ["rt", "fs", "io-util"] 17 | optional = true 18 | -------------------------------------------------------------------------------- /healer_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "healer_core" 3 | description = "core algorithm and data structure of healer" 4 | authors = ["Hao Sun "] 5 | version = "0.1.0" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | ahash = "0.7" 10 | rand = {version = "0.8", features = ["small_rng"]} 11 | log = "0.4" 12 | pest = "2.1" 13 | pest_derive = "2.1" 14 | hex = "0.4" 15 | tch = "0.7.0" 16 | reqwest = { version="0.11.10", features = ["blocking"] } -------------------------------------------------------------------------------- /tools/mutate_prog/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mutate_prog" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [[bin]] 7 | name = "mutate-prog" 8 | path = "src/main.rs" 9 | 10 | [dependencies] 11 | healer_core = {path = "../../healer_core"} 12 | syz_wrapper = {path = "../../syz_wrapper"} 13 | healer_alloc = {path = "../../healer_utils/healer_alloc"} 14 | rand = {version = "0.8", features = ["small_rng"]} 15 | env_logger = "0.9" 16 | log = "0.4" 17 | structopt = "0.3" -------------------------------------------------------------------------------- /syz_wrapper/patches/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 8 3 | UseTab: Always 4 | BreakBeforeBraces: Linux 5 | IndentCaseLabels: false 6 | DerivePointerAlignment: false 7 | PointerAlignment: Left 8 | AlignTrailingComments: true 9 | AllowShortBlocksOnASingleLine: false 10 | AllowShortFunctionsOnASingleLine: false 11 | AllowShortIfStatementsOnASingleLine: false 12 | AllowShortLoopsOnASingleLine: false 13 | ColumnLimit: 0 14 | AlignTrailingComments: false 15 | 16 | -------------------------------------------------------------------------------- /tools/model_manager/model_manager/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for model_manager project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'model_manager.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /tools/model_manager/model_manager/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for model_manager project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'model_manager.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /syz_wrapper/patches/targets.diff: -------------------------------------------------------------------------------- 1 | --- ./sys/targets/targets.go 2021-02-06 14:09:53.550804883 +0000 2 | +++ ./sys/targets/targets.go 2021-02-06 14:12:17.036224223 +0000 3 | @@ -35,7 +35,7 @@ 4 | KernelHeaderArch string 5 | BrokenCompiler string 6 | // NeedSyscallDefine is used by csource package to decide when to emit __NR_* defines. 7 | - NeedSyscallDefine func(nr uint64) bool 8 | + NeedSyscallDefine func(nr uint64) bool `json:"-"` 9 | HostEndian binary.ByteOrder 10 | SyscallTrampolines map[string]string 11 | 12 | -------------------------------------------------------------------------------- /tools/inspect_target/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inspect_target" 3 | description = "inspect target information" 4 | authors = ["Hao Sun "] 5 | version = "0.1.0" 6 | edition = "2018" 7 | 8 | [[bin]] 9 | name = "inspect-target" 10 | path = "src/main.rs" 11 | 12 | [dependencies] 13 | healer_core = {path = "../../healer_core"} 14 | syz_wrapper = {path = "../../syz_wrapper"} 15 | healer_alloc = {path = "../../healer_utils/healer_alloc"} 16 | rand = {version = "0.8", features = ["small_rng"]} 17 | structopt = "0.3" -------------------------------------------------------------------------------- /tools/gen_prog/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gen_prog" 3 | description = "Generate syscall prog" 4 | authors = ["Hao Sun "] 5 | version = "0.1.0" 6 | edition = "2018" 7 | 8 | [[bin]] 9 | name = "gen-prog" 10 | path = "src/main.rs" 11 | 12 | [dependencies] 13 | healer_core = {path = "../../healer_core"} 14 | syz_wrapper = {path = "../../syz_wrapper"} 15 | healer_alloc = {path = "../../healer_utils/healer_alloc"} 16 | rand = {version = "0.8", features = ["small_rng"]} 17 | env_logger = "0.9" 18 | log = "0.4" 19 | structopt = "0.3" 20 | -------------------------------------------------------------------------------- /healer_fuzzer/src/arch.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "x86_64")] 2 | pub static TARGET_ARCH: &str = "amd64"; 3 | 4 | #[cfg(target_arch = "x86")] 5 | pub static TARGET_ARCH: &str = "386"; 6 | 7 | #[cfg(target_arch = "aarch64")] 8 | pub static TARGET_ARCH: &str = "arm64"; 9 | 10 | #[cfg(target_arch = "arm")] 11 | pub static TARGET_ARCH: &str = "arm"; 12 | 13 | #[cfg(target_arch = "mips64el")] 14 | pub static TARGET_ARCH: &str = "mips64le"; 15 | 16 | #[cfg(target_arch = "ppc64")] 17 | pub static TARGET_ARCH: &str = "ppc64le"; 18 | 19 | #[cfg(target_arch = "riscv64")] 20 | pub static TARGET_ARCH: &str = "riscv64"; 21 | 22 | #[cfg(target_arch = "s390x")] 23 | pub static TARGET_ARCH: &str = "s390x"; 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "healer_core", 4 | "healer_fuzzer", 5 | "syz_wrapper", 6 | 7 | # utils 8 | "healer_utils/healer_alloc", 9 | "healer_utils/healer_vm", 10 | "healer_utils/healer_io", 11 | 12 | # tools mainly for debug purpose 13 | "tools/inspect_target", 14 | "tools/gen_prog", 15 | "tools/mutate_prog", 16 | "tools/minimize" 17 | ] 18 | 19 | [profile.dev] 20 | debug-assertions = true # enable verbose mode 21 | opt-level = 3 22 | 23 | [profile.release] 24 | debug = false 25 | debug-assertions = false 26 | overflow-checks = false 27 | lto = true 28 | panic = 'unwind' 29 | codegen-units = 1 30 | opt-level = 3 31 | 32 | [profile.test] 33 | opt-level = 3 -------------------------------------------------------------------------------- /healer_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Core algorithms and data structures of healer 2 | 3 | #[macro_use] 4 | extern crate pest_derive; 5 | 6 | use ahash::{AHashMap, AHashSet}; 7 | 8 | #[macro_use] 9 | pub mod verbose; 10 | pub mod alloc; 11 | pub mod context; 12 | pub mod corpus; 13 | pub mod gen; 14 | pub mod len; 15 | pub mod mutation; 16 | pub mod parse; 17 | pub mod prog; 18 | pub mod relation; 19 | pub mod select; 20 | pub mod syscall; 21 | pub mod target; 22 | pub mod ty; 23 | pub mod value; 24 | pub mod value_pool; 25 | pub mod lang_mod; 26 | pub mod scheduler; 27 | 28 | pub type HashMap = AHashMap; 29 | pub type HashSet = AHashSet; 30 | pub type RngType = rand::rngs::SmallRng; 31 | 32 | pub fn foo() { 33 | println!("hello mock"); 34 | } 35 | -------------------------------------------------------------------------------- /tools/model_manager/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'model_manager.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /syz_wrapper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "syz_wrapper" 3 | description = "Wrappers for syzkaller components" 4 | version = "0.1.0" 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ahash = "0.7" 9 | healer_core = {path = "../healer_core"} 10 | healer_io = {path = "../healer_utils/healer_io"} 11 | iota = "0.2" 12 | bytes = "1.1" 13 | thiserror = "1.0" 14 | shared_memory = "0.12" 15 | timeout-readwrite = "0.3.1" 16 | nix = "0.22" 17 | log = "0.4" 18 | 19 | [dependencies.simd-json] 20 | version = "0.4" 21 | features = ["known-key", "swar-number-parsing", "allow-non-simd"] 22 | 23 | [dev-dependencies] 24 | rand = {version = "0.8", features = ["small_rng"]} 25 | criterion = {version = "0.3", features = ["html_reports"]} 26 | 27 | [[bench]] 28 | name = "gen" 29 | harness = false 30 | 31 | [[bench]] 32 | name = "mutate" 33 | harness = false 34 | -------------------------------------------------------------------------------- /syz_wrapper/benches/gen.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use healer_core::{ 3 | gen::gen_prog, 4 | relation::{Relation, RelationWrapper}, lang_mod::model::ModelWrapper, scheduler::Scheduler, 5 | }; 6 | use rand::{prelude::SmallRng, SeedableRng}; 7 | use syz_wrapper::sys::{load_sys_target, SysTarget}; 8 | 9 | pub fn bench_prog_gen(c: &mut Criterion) { 10 | let target = load_sys_target(SysTarget::LinuxAmd64).unwrap(); 11 | let relation = Relation::new(&target); 12 | let rw = RelationWrapper::new(relation); 13 | let mw = ModelWrapper::default(); 14 | let sc = Scheduler::default(); 15 | let mut rng = SmallRng::from_entropy(); 16 | 17 | c.bench_function("prog-gen", |b| b.iter(|| gen_prog(&target, &rw, &mw, &sc, &mut rng))); 18 | } 19 | 20 | criterion_group!(benches, bench_prog_gen); 21 | criterion_main!(benches); 22 | -------------------------------------------------------------------------------- /healer_fuzzer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "healer_fuzzer" 3 | description = "Kernel fuzzer inspired by Syzkaller" 4 | authors = ["Hao Sun "] 5 | version = "0.1.0" 6 | edition = "2018" 7 | 8 | [[bin]] 9 | name = "healer" 10 | path = "src/main.rs" 11 | 12 | [dependencies] 13 | healer_core = {path = "../healer_core"} 14 | healer_alloc = {path = "../healer_utils/healer_alloc"} 15 | healer_vm = {path = "../healer_utils/healer_vm"} 16 | syz_wrapper = {path = "../syz_wrapper"} 17 | serde = {version = "1"} 18 | serde_derive = "1" 19 | serde_json = "1" 20 | ahash = "0.7" 21 | rand = {version = "0.8", features = ["small_rng"]} 22 | log = "0.4" 23 | structopt = "0.3" 24 | anyhow = "1.0" 25 | thiserror = "1.0" 26 | shared_memory = "0.12" 27 | signal-hook = {version = "0.3", features = ["iterator", "extended-siginfo"]} 28 | sha-1 = "0.9" 29 | hex = "0.4" 30 | env_logger = "0.9" 31 | regex = "1.5" -------------------------------------------------------------------------------- /healer_utils/healer_alloc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "healer_alloc" 3 | description = "Healer allocator" 4 | authors = ["Hao Sun "] 5 | version = "0.1.0" 6 | edition = "2018" 7 | 8 | [features] 9 | default = ["jemalloc"] 10 | jemalloc = ["tikv-jemallocator", "tikv-jemalloc-ctl", "tikv-jemalloc-sys"] 11 | snmalloc = ["snmalloc-rs"] 12 | 13 | [dependencies.tikv-jemallocator] 14 | version = "0.4" 15 | optional = true 16 | features = ["unprefixed_malloc_on_supported_platforms"] 17 | 18 | [dependencies.tikv-jemalloc-sys] 19 | version = "0.4" 20 | optional = true 21 | 22 | [dependencies.tikv-jemalloc-ctl] 23 | version = "0.4" 24 | optional = true 25 | 26 | [dependencies.tcmalloc] 27 | version = "0.3" 28 | optional = true 29 | features = ["bundled"] 30 | 31 | [dependencies.mimalloc] 32 | version = "0.1" 33 | optional = true 34 | default-features = false 35 | 36 | [dependencies.snmalloc-rs] 37 | version = "0.2" 38 | optional = true -------------------------------------------------------------------------------- /tools/model_manager/model_manager/urls.py: -------------------------------------------------------------------------------- 1 | """model_manager URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | 19 | from api import views 20 | urlpatterns = [ 21 | path('admin/', admin.site.urls), 22 | path('api/model', views.model_train, name="model_train"), 23 | ] 24 | -------------------------------------------------------------------------------- /syz_wrapper/patches/executor.diff: -------------------------------------------------------------------------------- 1 | diff --git a/executor/executor.cc b/executor/executor.cc 2 | index 73f6053ee..a2552f1df 100644 3 | --- a/executor/executor.cc 4 | +++ b/executor/executor.cc 5 | @@ -407,6 +407,9 @@ static void setup_features(char** enable, int n); 6 | #endif 7 | 8 | #include "cov_filter.h" 9 | +#include "features.h" 10 | +#include "ivshm_setup.h" 11 | +#include "unix_sock_setup.h" 12 | 13 | #include "test.h" 14 | 15 | @@ -444,12 +447,18 @@ int main(int argc, char** argv) 16 | return 1; 17 | } 18 | 19 | + FEATURES_CHECK_SNIPPET; 20 | + 21 | + // NOTE Must be called before setup_control_pipes 22 | + SETUP_UNIX_SOCKS_SNIPPET; 23 | + 24 | start_time_ms = current_time_ms(); 25 | 26 | os_init(argc, argv, (char*)SYZ_DATA_OFFSET, SYZ_NUM_PAGES * SYZ_PAGE_SIZE); 27 | current_thread = &threads[0]; 28 | 29 | #if SYZ_EXECUTOR_USES_SHMEM 30 | + IVSHM_SETUP_SNIPPET; 31 | if (mmap(&input_data[0], kMaxInput, PROT_READ, MAP_PRIVATE | MAP_FIXED, kInFd, 0) != &input_data[0]) 32 | fail("mmap of input file failed"); 33 | 34 | -------------------------------------------------------------------------------- /healer_core/src/mutation/res.rs: -------------------------------------------------------------------------------- 1 | //! Mutate value of `resource` type. 2 | #[cfg(debug_assertions)] 3 | use crate::mutation::call::display_value_diff; 4 | use crate::{ 5 | context::Context, 6 | gen::{current_builder, res::gen_res}, 7 | ty::Dir, 8 | value::Value, 9 | RngType, 10 | }; 11 | 12 | pub fn mutate_res(ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 13 | if val.dir() == Dir::Out { 14 | return false; // do not change output resource id 15 | } 16 | 17 | let ty = val.ty(ctx.target); 18 | let new_val = gen_res(ctx, rng, ty, val.dir()); 19 | // update call's used res 20 | let ty = ty.checked_as_res(); 21 | if let Some(id) = new_val.checked_as_res().res_val_id() { 22 | current_builder(|b| { 23 | let ent = b.used_res.entry(ty.res_name().clone()).or_default(); 24 | ent.insert(id); 25 | }) 26 | } 27 | debug_info!( 28 | "mutate_res: {}", 29 | display_value_diff(val, &new_val, ctx.target) 30 | ); 31 | let mutated = new_val != *val; 32 | *val = new_val; 33 | 34 | mutated 35 | } 36 | -------------------------------------------------------------------------------- /healer_fuzzer/src/fuzzer_log.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | 3 | thread_local! { 4 | static FUZZER_ID: Cell = Cell::new(0); 5 | } 6 | 7 | #[inline] 8 | pub fn set_fuzzer_id(id: u64) { 9 | FUZZER_ID.with(|r| r.set(id)); 10 | } 11 | 12 | #[inline] 13 | pub fn fuzzer_id() -> u64 { 14 | FUZZER_ID.with(|r| r.get()) 15 | } 16 | 17 | #[macro_export] 18 | macro_rules! fuzzer_debug { 19 | ($t: tt, $($arg:tt)*) => ( 20 | log::debug!(std::concat!("fuzzer-{}: ", $t), crate::fuzzer_log::fuzzer_id(), $($arg)*) 21 | ) 22 | } 23 | 24 | #[macro_export] 25 | macro_rules! fuzzer_info { 26 | ($t: tt, $($arg:tt)*) => ( 27 | log::info!(std::concat!("fuzzer-{}: ", $t), crate::fuzzer_log::fuzzer_id(), $($arg)*) 28 | ) 29 | } 30 | 31 | #[macro_export] 32 | macro_rules! fuzzer_warn { 33 | ($t: tt, $($arg:tt)*) => ( 34 | log::warn!(std::concat!("fuzzer-{}: ", $t), crate::fuzzer_log::fuzzer_id(), $($arg)*) 35 | ) 36 | } 37 | 38 | #[macro_export] 39 | macro_rules! fuzzer_error { 40 | ($t: tt, $($arg:tt)*) => ( 41 | log::error!(std::concat!("fuzzer-{}: ", $t), crate::fuzzer_log::fuzzer_id(), $($arg)*) 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /healer_utils/healer_io/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{AtomicBool, Ordering}, 3 | Arc, Mutex, 4 | }; 5 | 6 | pub mod thread; 7 | #[cfg(features = "tokio-io")] 8 | pub mod tokio; 9 | 10 | #[derive(Debug)] 11 | pub struct BackgroundIoHandle { 12 | buf: Arc>>, 13 | finished: Arc, 14 | } 15 | 16 | impl BackgroundIoHandle { 17 | fn new(buf: Arc>>, finished: Arc) -> Self { 18 | Self { buf, finished } 19 | } 20 | 21 | pub fn current_data(&self) -> Vec { 22 | let mut buf = self.buf.lock().unwrap(); 23 | buf.split_off(0) 24 | } 25 | 26 | pub fn clear_current(&self) { 27 | let mut buf = self.buf.lock().unwrap(); 28 | buf.clear(); 29 | } 30 | 31 | pub fn wait_finish(self) -> Vec { 32 | while !self.finished.load(Ordering::Relaxed) {} 33 | self.current_data() 34 | } 35 | } 36 | 37 | impl Clone for BackgroundIoHandle { 38 | fn clone(&self) -> Self { 39 | BackgroundIoHandle { 40 | buf: Arc::clone(&self.buf), 41 | finished: Arc::clone(&self.finished), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /healer_core/src/mutation/ptr.rs: -------------------------------------------------------------------------------- 1 | //! Mutate value of `ptr`, `vma` type. 2 | use super::call::contains_out_res; 3 | #[cfg(debug_assertions)] 4 | use super::call::display_value_diff; 5 | use crate::{ 6 | context::Context, 7 | gen::ptr::gen_vma, 8 | value::{PtrValue, Value}, 9 | RngType, 10 | }; 11 | use rand::Rng; 12 | 13 | pub fn mutate_ptr(_ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 14 | if contains_out_res(val) { 15 | return false; 16 | } 17 | 18 | if rng.gen_ratio(1, 1000) { 19 | // set null 20 | let new_val = PtrValue::new_special(val.ty_id(), val.dir(), 0).into(); 21 | debug_info!( 22 | "mutate_ptr: {}", 23 | display_value_diff(val, &new_val, _ctx.target) 24 | ); 25 | *val = new_val; 26 | return true; 27 | } 28 | 29 | false 30 | } 31 | 32 | pub fn mutate_vma(ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 33 | let ty = val.ty(ctx.target); 34 | let new_val = gen_vma(ctx, rng, ty, val.dir()); 35 | debug_info!( 36 | "mutate_vma: {}", 37 | display_value_diff(val, &new_val, ctx.target) 38 | ); 39 | *val = new_val; 40 | 41 | true 42 | } 43 | -------------------------------------------------------------------------------- /healer_utils/healer_io/src/thread.rs: -------------------------------------------------------------------------------- 1 | use crate::BackgroundIoHandle; 2 | use std::io::BufReader; 3 | use std::{ 4 | fs::File, 5 | io::BufRead, 6 | os::unix::prelude::{FromRawFd, IntoRawFd}, 7 | sync::{ 8 | atomic::{AtomicBool, Ordering}, 9 | Arc, Mutex, 10 | }, 11 | }; 12 | 13 | pub fn read_background(f: T, debug: bool) -> BackgroundIoHandle { 14 | let fd = f.into_raw_fd(); 15 | let f = unsafe { File::from_raw_fd(fd) }; 16 | let buf = Arc::new(Mutex::new(Vec::with_capacity(4096))); 17 | let finished = Arc::new(AtomicBool::new(false)); 18 | let buf1 = Arc::clone(&buf); 19 | let finished1 = Arc::clone(&finished); 20 | 21 | std::thread::spawn(move || { 22 | let mut buf = String::with_capacity(4096); 23 | let mut reader = BufReader::new(f); 24 | while let Ok(sz) = reader.read_line(&mut buf) { 25 | if sz == 0 { 26 | break; 27 | } 28 | let mut shared_buf = buf1.lock().unwrap(); 29 | shared_buf.extend(buf[..sz].as_bytes()); 30 | if debug { 31 | print!("{}", &buf[..sz]); 32 | } 33 | buf.clear(); 34 | } 35 | finished1.store(true, Ordering::Relaxed); 36 | }); 37 | 38 | BackgroundIoHandle::new(buf, finished) 39 | } 40 | -------------------------------------------------------------------------------- /syz_wrapper/src/exec/message.rs: -------------------------------------------------------------------------------- 1 | //! Communication messages with syz-executor 2 | 3 | pub const IN_MAGIC: u64 = 0xBADC0FFEEBADFACE; 4 | pub const OUT_MAGIC: u32 = 0xBADF00D; 5 | 6 | #[repr(C)] 7 | #[derive(Default, Debug)] 8 | pub struct HandshakeReq { 9 | pub magic: u64, 10 | pub env_flags: u64, // env flags 11 | pub pid: u64, 12 | } 13 | #[repr(C)] 14 | #[derive(Default, Debug)] 15 | pub struct HandshakeReply { 16 | pub magic: u32, 17 | } 18 | 19 | #[repr(C)] 20 | #[derive(Default, Debug)] 21 | pub struct ExecuteReq { 22 | pub magic: u64, 23 | pub env_flags: u64, // env flags 24 | pub exec_flags: u64, // exec flags 25 | pub pid: u64, 26 | pub fault_call: u64, 27 | pub fault_nth: u64, 28 | pub syscall_timeout_ms: u64, 29 | pub program_timeout_ms: u64, 30 | pub slowdown_scale: u64, 31 | pub prog_size: u64, 32 | } 33 | 34 | #[repr(C)] 35 | #[derive(Default, Debug)] 36 | pub struct ExecuteReply { 37 | pub magic: u32, 38 | pub done: u32, 39 | pub status: u32, 40 | } 41 | 42 | #[repr(C)] 43 | #[derive(Default, Debug)] 44 | pub struct CallReply { 45 | pub index: u32, // call index in the program 46 | pub num: u32, // syscall number (for cross-checking) 47 | pub errno: u32, 48 | pub flags: u32, // see CallFlags 49 | pub branch_size: u32, 50 | pub block_size: u32, 51 | pub comps_size: u32, 52 | } 53 | -------------------------------------------------------------------------------- /healer_utils/healer_alloc/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Allocators 2 | #[cfg(all(unix, target_arch = "x86_64", feature = "jemalloc"))] 3 | mod alloc { 4 | pub type Allocator = tikv_jemallocator::Jemalloc; 5 | 6 | pub const fn allocator() -> Allocator { 7 | tikv_jemallocator::Jemalloc 8 | } 9 | } 10 | #[cfg(all(unix, feature = "tcmalloc"))] 11 | mod alloc { 12 | pub type Allocator = tcmalloc::TCMalloc; 13 | 14 | pub const fn allocator() -> Allocator { 15 | tcmalloc::TCMalloc 16 | } 17 | } 18 | #[cfg(all(unix, feature = "mimalloc"))] 19 | mod alloc { 20 | pub type Allocator = mimalloc::MiMalloc; 21 | 22 | pub const fn allocator() -> Allocator { 23 | mimalloc::MiMalloc 24 | } 25 | } 26 | #[cfg(all(unix, feature = "snmalloc"))] 27 | mod alloc { 28 | pub type Allocator = snmalloc_rs::SnMalloc; 29 | 30 | pub const fn allocator() -> Allocator { 31 | snmalloc_rs::SnMalloc 32 | } 33 | } 34 | #[cfg(not(all( 35 | unix, 36 | any( 37 | all(feature = "jemalloc", target_arch = "x86_64"), 38 | feature = "tcmalloc", 39 | feature = "mimalloc", 40 | feature = "snmalloc" 41 | ) 42 | )))] 43 | mod alloc { 44 | pub type Allocator = std::alloc::System; 45 | pub const fn allocator() -> Allocator { 46 | std::alloc::System 47 | } 48 | } 49 | 50 | #[global_allocator] 51 | static ALLOC: alloc::Allocator = alloc::allocator(); 52 | -------------------------------------------------------------------------------- /tools/gen_prog/src/main.rs: -------------------------------------------------------------------------------- 1 | use healer_core::gen; 2 | use healer_core::lang_mod::model::ModelWrapper; 3 | use healer_core::relation::{Relation, RelationWrapper}; 4 | use healer_core::scheduler::Scheduler; 5 | use healer_core::verbose::set_verbose; 6 | use rand::prelude::*; 7 | use rand::rngs::SmallRng; 8 | use std::process::exit; 9 | use structopt::StructOpt; 10 | use syz_wrapper::sys::load_target; 11 | 12 | #[derive(Debug, StructOpt)] 13 | struct Settings { 14 | /// Target to inspect. 15 | #[structopt(long, default_value = "linux/amd64")] 16 | target: String, 17 | /// Number of progs to generate. 18 | #[structopt(long, short, default_value = "1")] 19 | n: usize, 20 | /// Verbose. 21 | #[structopt(long)] 22 | verbose: bool, 23 | } 24 | 25 | fn main() { 26 | let settings = Settings::from_args(); 27 | env_logger::init(); 28 | set_verbose(settings.verbose); 29 | let target = load_target(&settings.target).unwrap_or_else(|e| { 30 | eprintln!("failed to load target: {}", e); 31 | exit(1) 32 | }); 33 | let relation = Relation::new(&target); 34 | let rw = RelationWrapper::new(relation); 35 | let mw = ModelWrapper::default(); 36 | let sc = Scheduler::default(); 37 | let mut rng = SmallRng::from_entropy(); 38 | for _ in 0..settings.n { 39 | let p = gen::gen_prog(&target, &rw, &mw, &sc, &mut rng); 40 | println!("{}\n", p.display(&target)); 41 | } 42 | exit(0); 43 | } 44 | -------------------------------------------------------------------------------- /syz_wrapper/patches/unix_sock_setup.h: -------------------------------------------------------------------------------- 1 | #ifndef HEALER_UNIX_SOCK 2 | #define HEALER_UNIX_SOCK 3 | 4 | #if GOOS_linux 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define PORT_STDIN 30 14 | #define PORT_STDOUT 29 15 | #define PORT_STDERR 28 16 | 17 | int open_vport_dev(int id, int port) 18 | { 19 | char buf[FILENAME_MAX]; 20 | int fd; 21 | 22 | snprintf(buf, FILENAME_MAX, "/dev/vport%dp%d", id, port); 23 | fd = open(buf, O_RDWR); 24 | if (fd < 0) { 25 | failmsg("failed to open: ", "%s", buf); 26 | } 27 | return fd; 28 | } 29 | 30 | void setup_unix_sock() 31 | { 32 | int mappings[3][2] = {{PORT_STDIN, 0}, {PORT_STDOUT, 1}, {PORT_STDERR, 2}}; 33 | int i = 0; 34 | int fd; 35 | 36 | for (; i != 3; i++) { 37 | fd = open_vport_dev(i + 1, mappings[i][0]); 38 | if (dup2(fd, mappings[i][1]) < 0) { 39 | failmsg("failed to dup:", "%d -> %d", fd, mappings[i][1]); 40 | } 41 | close(fd); 42 | } 43 | } 44 | 45 | #define SETUP_UNIX_SOCKS_SNIPPET \ 46 | do { \ 47 | if (argc >= 3 && strcmp(argv[2], "use-unix-socks") == 0) { \ 48 | setup_unix_sock(); \ 49 | } \ 50 | } while (0) 51 | 52 | #else 53 | #error Currently, ivshm_setup only supports linux. 54 | #endif // GOOS_linux 55 | 56 | #endif // HEALER_UNIX_SOCK -------------------------------------------------------------------------------- /healer_core/src/gen/group.rs: -------------------------------------------------------------------------------- 1 | //! Generate value for `array`, `struct`, `union` type. 2 | use crate::{ 3 | context::Context, 4 | gen::gen_ty_value, 5 | ty::{Dir, Type}, 6 | value::{GroupValue, UnionValue, Value}, 7 | RngType, 8 | }; 9 | use rand::prelude::*; 10 | use std::ops::RangeInclusive; 11 | 12 | pub fn gen_array(ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 13 | let ty = ty.checked_as_array(); 14 | let elem_ty = ty.elem(); 15 | let range = ty.range().unwrap_or(RangeInclusive::new(0, 10)); 16 | let len = rng.gen_range(range); 17 | let elems = (0..len) 18 | .map(|_| gen_ty_value(ctx, rng, elem_ty, dir)) 19 | .collect::>(); 20 | GroupValue::new(ty.id(), dir, elems).into() 21 | } 22 | 23 | pub fn gen_struct(ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 24 | let ty = ty.checked_as_struct(); 25 | let mut fields = Vec::with_capacity(ty.fields().len()); 26 | for field in ty.fields() { 27 | let dir = field.dir().unwrap_or(dir); 28 | fields.push(gen_ty_value(ctx, rng, field.ty(), dir)); 29 | } 30 | GroupValue::new(ty.id(), dir, fields).into() 31 | } 32 | 33 | pub fn gen_union(ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 34 | let ty = ty.checked_as_union(); 35 | let field_to_gen = rng.gen_range(0..ty.fields().len()); 36 | let field = &ty.fields()[field_to_gen]; 37 | let field_ty = field.ty(); 38 | let field_dir = field.dir().unwrap_or(dir); 39 | let val = gen_ty_value(ctx, rng, field_ty, field_dir); 40 | UnionValue::new(ty.id(), dir, field_to_gen as u64, val).into() 41 | } 42 | -------------------------------------------------------------------------------- /tools/model_manager/api/lang_model/train.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import torch 4 | import torch.nn as nn 5 | from torch.utils.data import DataLoader 6 | import torch.optim as optim 7 | 8 | from utils import read_corpus, read_syscalls 9 | from dataset import Vocab, SysDataset 10 | from model import RelationModel, train, eval 11 | 12 | def test(argv): 13 | corpus_file = argv[1] 14 | testcase_file = argv[2] 15 | model_path = argv[3] 16 | 17 | syscalls = read_syscalls(r"/home/data/syscall_names") 18 | corpus = read_corpus(corpus_file) 19 | 20 | vocab = Vocab() 21 | vocab.addDict(syscalls) 22 | vocab.addCorpus(corpus) 23 | sys_dataset = SysDataset(corpus, vocab) 24 | 25 | train_size = int(0.8*len(sys_dataset)) 26 | test_size = len(sys_dataset)-train_size 27 | train_dataset, test_dataset = torch.utils.data.random_split(sys_dataset, [train_size, test_size]) 28 | 29 | train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) 30 | test_loader = DataLoader(test_dataset, batch_size=64, shuffle=True) 31 | 32 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 33 | 34 | lr = 0.001 35 | embed_dim = 64 36 | hidden_dim = 128 37 | 38 | model = RelationModel(hidden_dim, embed_dim, len(sys_dataset.vocab), device).to(device) 39 | optimizer = optim.Adam(model.parameters(), lr=lr) 40 | criterion = nn.CrossEntropyLoss() 41 | 42 | num_epoch = 50 43 | model_file = train(train_loader, test_loader, model, \ 44 | model_path, num_epoch, optimizer, \ 45 | lr, criterion, device) 46 | 47 | if __name__ == "__main__": 48 | test(sys.argv) 49 | -------------------------------------------------------------------------------- /syz_wrapper/benches/mutate.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use healer_core::{ 3 | corpus::CorpusWrapper, 4 | gen::{self, set_prog_len_range, FAVORED_MIN_PROG_LEN}, 5 | mutation::mutate, 6 | relation::{Relation, RelationWrapper}, 7 | target::Target, lang_mod::model::ModelWrapper, scheduler::Scheduler, 8 | }; 9 | use rand::prelude::*; 10 | use syz_wrapper::sys::{load_sys_target, SysTarget}; 11 | 12 | pub fn bench_prog_mutation(c: &mut Criterion) { 13 | let target = load_sys_target(SysTarget::LinuxAmd64).unwrap(); 14 | let relation = Relation::new(&target); 15 | let rw = RelationWrapper::new(relation); 16 | let mw = ModelWrapper::default(); 17 | let sc = Scheduler::default(); 18 | let mut rng = SmallRng::from_entropy(); 19 | let corpus = dummy_corpus(&target, &rw, &mw, &sc, &mut rng); 20 | 21 | c.bench_function("prog-mutation", |b| { 22 | b.iter(|| { 23 | let mut p = corpus.select_one(&mut rng).unwrap(); 24 | mutate(&target, &rw, &mw, &corpus, &sc, &mut rng, &mut p); 25 | }) 26 | }); 27 | } 28 | 29 | fn dummy_corpus(target: &Target, relation: &RelationWrapper, model: &ModelWrapper, 30 | scheduler: &Scheduler ,rng: &mut SmallRng) -> CorpusWrapper { 31 | let corpus = CorpusWrapper::new(); 32 | let n = rng.gen_range(512..=4096); 33 | set_prog_len_range(3..8); // progs in corpus are always shorter 34 | for _ in 0..n { 35 | let prio = rng.gen_range(64..=1024); 36 | corpus.add_prog(gen::gen_prog(target, relation, model, scheduler, rng), prio); 37 | } 38 | set_prog_len_range(FAVORED_MIN_PROG_LEN..FAVORED_MIN_PROG_LEN); // restore 39 | corpus 40 | } 41 | 42 | criterion_group!(benches, bench_prog_mutation); 43 | criterion_main!(benches); 44 | -------------------------------------------------------------------------------- /healer_fuzzer/src/feedback.rs: -------------------------------------------------------------------------------- 1 | use healer_core::HashSet; 2 | use std::sync::RwLock; 3 | 4 | #[derive(Debug, Default)] 5 | pub struct Feedback { 6 | max_cov: RwLock>, 7 | cal_cov: RwLock>, // calibrated cover 8 | } 9 | 10 | impl Feedback { 11 | pub fn new() -> Self { 12 | Self::default() 13 | } 14 | 15 | pub fn check_max_cov(&self, cov: impl IntoIterator) -> HashSet { 16 | Self::check_inner(&self.max_cov, cov, true) 17 | } 18 | 19 | pub fn check_cal_cov(&self, cov: impl IntoIterator) -> HashSet { 20 | Self::check_inner(&self.cal_cov, cov, false) 21 | } 22 | 23 | pub fn merge(&self, new: &HashSet) { 24 | Self::merge_inner(&self.max_cov, new); 25 | Self::merge_inner(&self.cal_cov, new); 26 | } 27 | 28 | pub fn max_cov_len(&self) -> usize { 29 | let m = self.max_cov.read().unwrap(); 30 | m.len() 31 | } 32 | 33 | pub fn cal_cov_len(&self) -> usize { 34 | let m = self.cal_cov.read().unwrap(); 35 | m.len() 36 | } 37 | 38 | fn check_inner( 39 | cov: &RwLock>, 40 | cov_in: impl IntoIterator, 41 | merge: bool, 42 | ) -> HashSet { 43 | let mut new = HashSet::new(); 44 | { 45 | let mc = cov.read().unwrap(); 46 | for c in cov_in { 47 | if !mc.contains(&c) { 48 | new.insert(c); 49 | } 50 | } 51 | } 52 | 53 | if !new.is_empty() && merge { 54 | Self::merge_inner(cov, &new); 55 | } 56 | 57 | new 58 | } 59 | 60 | fn merge_inner(cov: &RwLock>, new: &HashSet) { 61 | let mut cov = cov.write().unwrap(); 62 | cov.extend(new) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /healer_core/src/verbose.rs: -------------------------------------------------------------------------------- 1 | #[cfg(debug_assertions)] 2 | use std::cell::Cell; 3 | 4 | #[cfg(debug_assertions)] 5 | thread_local! { 6 | static VERBOSE: Cell = Cell::new(false); 7 | } 8 | 9 | #[cfg(debug_assertions)] 10 | pub fn set_verbose(verbose: bool) { 11 | VERBOSE.with(|v| v.set(verbose)) 12 | } 13 | 14 | #[cfg(debug_assertions)] 15 | #[macro_export] 16 | macro_rules! debug_info { 17 | (target: $target:expr, $($arg:tt)+) => ( 18 | if crate::verbose::verbose_mode(){ 19 | log::info!(target: $target, $crate::Level::Error, $($arg)+) 20 | } 21 | ); 22 | ($($arg:tt)+) => ( 23 | if crate::verbose::verbose_mode(){ 24 | log::info!($($arg)+) 25 | } 26 | ) 27 | } 28 | 29 | #[cfg(debug_assertions)] 30 | #[macro_export] 31 | macro_rules! debug_warn { 32 | (target: $target:expr, $($arg:tt)+) => ( 33 | if crate::verbose::verbose_mode(){ 34 | log::warn!(target: $target, $crate::Level::Error, $($arg)+) 35 | } 36 | ); 37 | ($($arg:tt)+) => ( 38 | if crate::verbose::verbose_mode(){ 39 | log::warn!($($arg)+) 40 | } 41 | ) 42 | } 43 | 44 | #[cfg(debug_assertions)] 45 | #[inline] 46 | pub(crate) fn verbose_mode() -> bool { 47 | VERBOSE.with(|v| v.get()) 48 | } 49 | 50 | #[cfg(not(debug_assertions))] 51 | #[inline(always)] 52 | pub fn set_verbose(_verbose: bool) {} 53 | 54 | #[cfg(not(debug_assertions))] 55 | #[macro_export] 56 | macro_rules! debug_info { 57 | (target: $target:expr, $($arg:tt)+) => {}; 58 | ($($arg:tt)+) => {}; 59 | } 60 | 61 | #[cfg(not(debug_assertions))] 62 | #[macro_export] 63 | macro_rules! debug_warn { 64 | (target: $target:expr, $($arg:tt)+) => {}; 65 | ($($arg:tt)+) => {}; 66 | } 67 | 68 | #[cfg(not(debug_assertions))] 69 | pub(crate) fn verbose_mode() -> bool { 70 | false 71 | } 72 | -------------------------------------------------------------------------------- /healer_core/src/lang_mod/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{read_to_string} 3 | }; 4 | use tch::{Tensor, IndexOp, kind}; 5 | 6 | #[allow(dead_code)] 7 | pub fn read_syscall>(path: P) -> Vec{ 8 | let content = read_to_string(path).unwrap(); 9 | let syscalls = content.split(" ").map(|x| x.to_string()).collect(); 10 | syscalls 11 | } 12 | 13 | #[allow(dead_code)] 14 | pub fn accuracy(predict: &Tensor, labels: &Tensor, topk: Vec) -> Vec{ 15 | let maxk = *topk.last().unwrap(); 16 | let pred = predict.topk(maxk, 1, true, true); 17 | let pred_indexes = pred.1.transpose(1, 0); 18 | 19 | let pad_mask = Tensor::zeros(&labels.size(), kind::INT64_CPU); 20 | let pad_mask = pad_mask.ne_tensor(&labels); 21 | 22 | let correct = pred_indexes.eq_tensor(&labels.view([1,-1]).expand_as(&pred_indexes)); 23 | let correct = correct.logical_and(&pad_mask); 24 | 25 | let total = f64::from(pad_mask.sum(tch::Kind::Float)); 26 | let mut ret: Vec = Vec::new(); 27 | for k in topk { 28 | let correct_k = correct.i(0..k).reshape(&[1,-1]).sum(tch::Kind::Float); 29 | let val = f64::trunc(f64::from(correct_k*100.0*100.0/total))/100.0; 30 | ret.push(val); 31 | } 32 | ret 33 | } 34 | 35 | #[allow(dead_code)] 36 | pub struct AverageMetrics { 37 | total: f64, 38 | cnt: usize 39 | } 40 | 41 | #[allow(dead_code)] 42 | impl AverageMetrics { 43 | pub fn new() -> Self { 44 | AverageMetrics { total: 0., cnt: 0} 45 | } 46 | 47 | pub fn update(&mut self, loss: f64, cnt: usize) { 48 | self.total += loss; 49 | self.cnt += cnt; 50 | } 51 | 52 | pub fn avg(&self) -> f64 { 53 | self.total / self.cnt as f64 54 | } 55 | 56 | pub fn reset(&mut self) { 57 | self.total = 0.; 58 | self.cnt = 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /healer_core/src/parse/prog_syntax.pest: -------------------------------------------------------------------------------- 1 | // Grammer for prog 2 | 3 | Prog = _{SOI ~ Call* ~ EOI} 4 | 5 | Call = {Ret? ~ CallName ~ (EmptyArg | Args)} 6 | Ret = {ResName ~ "="} 7 | EmptyArg = _{"(" ~ ")"} 8 | Args = _{"(" ~ Value ~ ("," ~ Value)* ~ ")"} 9 | 10 | Value = {Auto | Int | Data | Ptr | Vma | Res | Array | Struct | Union} 11 | 12 | Auto = {"AUTO"} 13 | 14 | /// Rule for `ConstValue` 15 | /// Value that confirms to `Int` rule could be special res value, speical ptr too. 16 | Int = @{("0x" | "0X") ~ ASCII_HEX_DIGIT+} 17 | 18 | /// Rule for `DataValue` 19 | Data = {ReadableData | NonReadableData} 20 | ReadableData = ${"'" ~ EscapedStr ~ "'" ~ DataLen?} 21 | EscapedStr = @{(!"'" ~("\\'" | "\\\\" | ANY))*} 22 | DataLen = _{"/" ~ Number} 23 | // This could also be an output data value 24 | NonReadableData = ${"\"" ~ HexEncodedStr ~ "\"" ~ DataLen?} 25 | HexEncodedStr = @{ASCII_HEX_DIGIT*} 26 | 27 | /// Rule for `PtrValue` 28 | Ptr = {"&" ~ (Auto | ("(" ~ (Int) ~ ")")) ~ ("=" ~ AnyPtr? ~ (Nil | Value))?} 29 | AnyPtr = {"ANY" ~ "="} 30 | Nil = {"nil"} 31 | /// Rule for `VmaValue` 32 | Vma = {"&" ~ "(" ~ Int ~ "/" ~ Int ~ ")" ~ "=" ~ "nil"} 33 | 34 | /// Rule for `ResValue` 35 | Res = {OutRes | InRes} 36 | OutRes = {"<" ~ ResName ~ "=>" ~ Int?} 37 | InRes = {ResName ~ ResOpDiv? ~ ResOpAdd?} 38 | ResOpDiv = {"/" ~ Number} 39 | ResOpAdd = {"+" ~ Number} 40 | 41 | /// Rule for `GroupValue` 42 | Array = {EmptyArray | NonEmptyArray} 43 | EmptyArray = _{"[" ~ "]"} 44 | NonEmptyArray = _{"[" ~ Value ~ ("," ~ Value)* ~ "]"} 45 | Struct = {EmptyStruct | NonEmptyStruct} 46 | EmptyStruct = _{"{" ~ "}"} 47 | NonEmptyStruct = _{"{" ~ Value ~ ("," ~ Value)* ~ "}"} 48 | 49 | /// Rule for `UnionValue` 50 | Union = {"@" ~ Ident ~ ("=" ~ Value)?} 51 | 52 | CallName = @{Ident ~ ("$" ~ (ASCII_ALPHANUMERIC | "_")+)?} 53 | ResName = ${"r" ~ Number} 54 | Number = @{ASCII_DIGIT+} 55 | Ident = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } 56 | 57 | WHITESPACE = _{" " | "\t" | "\r" | "\n"} 58 | COMMENT = _{"#" ~ (!"\n" ~ ANY)*} -------------------------------------------------------------------------------- /syz_wrapper/src/exec/util.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use core::slice; 3 | use std::{ 4 | io::{Read, Write}, 5 | mem, 6 | }; 7 | 8 | pub fn read_u32(buf: &mut &[u8]) -> Option { 9 | if buf.remaining() >= 4 { 10 | let val = if cfg!(target_endian = "little") { 11 | buf.get_u32_le() 12 | } else { 13 | buf.get_u32() 14 | }; 15 | Some(val) 16 | } else { 17 | None 18 | } 19 | } 20 | 21 | pub fn read_u32_slice<'a>(buf: &mut &'a [u8], len: usize) -> Option<&'a [u32]> { 22 | let l = len * mem::size_of::(); 23 | if l <= buf.len() { 24 | let ret = unsafe { slice::from_raw_parts(buf.as_ptr() as *const u32, len) }; 25 | buf.advance(l); 26 | Some(ret) 27 | } else { 28 | None 29 | } 30 | } 31 | 32 | pub fn read<'a, T: Sized>(buf: &mut &'a [u8]) -> Option<&'a T> { 33 | let sz = mem::size_of::(); 34 | if buf.len() >= sz { 35 | let buf0 = &buf[0..sz]; 36 | let v = cast_from(buf0); 37 | buf.advance(sz); 38 | Some(v) 39 | } else { 40 | None 41 | } 42 | } 43 | 44 | pub fn read_exact(mut r: R) -> Result { 45 | let mut v = T::default(); 46 | let data = cast_to_mut(&mut v); 47 | r.read_exact(data)?; 48 | Ok(v) 49 | } 50 | 51 | pub fn write_all(mut w: W, v: &T) -> Result<(), std::io::Error> { 52 | let data = cast_to(v); 53 | w.write_all(data) 54 | } 55 | 56 | pub fn cast_to(v: &T) -> &[u8] { 57 | let ptr = (v as *const T).cast::(); 58 | let len = mem::size_of::(); 59 | unsafe { slice::from_raw_parts(ptr, len) } 60 | } 61 | 62 | pub fn cast_to_mut(v: &mut T) -> &mut [u8] { 63 | let ptr = (v as *mut T).cast::(); 64 | let len = mem::size_of::(); 65 | unsafe { slice::from_raw_parts_mut(ptr, len) } 66 | } 67 | 68 | pub fn cast_from(v: &[u8]) -> &T { 69 | assert_eq!(v.len(), mem::size_of::()); 70 | let ptr = v.as_ptr() as *const T; 71 | unsafe { ptr.as_ref().unwrap() } 72 | } 73 | -------------------------------------------------------------------------------- /healer_utils/healer_vm/src/ssh.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::process::Command; 3 | 4 | use thiserror::Error; 5 | 6 | pub fn ssh_basic_cmd>( 7 | ip: T, 8 | port: u16, 9 | key: T, // use key 10 | user: T, 11 | ) -> Command { 12 | let mut ssh_cmd = Command::new("ssh"); 13 | ssh_cmd 14 | .args(vec!["-F", "/dev/null"]) 15 | .args(vec!["-o", "BatchMode=yes"]) 16 | .args(vec!["-o", "IdentitiesOnly=yes"]) 17 | .args(vec!["-o", "StrictHostKeyChecking=no"]) 18 | .args(vec!["-o", "UserKnownHostsFile=/dev/null"]) 19 | .args(vec!["-o", "ConnectTimeout=3s"]) 20 | .args(vec!["-i", key.as_ref()]) 21 | .args(vec!["-p", &format!("{}", port)]) 22 | .arg(format!("{}@{}", user.as_ref(), ip.as_ref())); 23 | ssh_cmd 24 | } 25 | 26 | #[derive(Debug, Error)] 27 | pub enum ScpError { 28 | #[error("scp: {0}")] 29 | Scp(String), 30 | #[error("spawn: {0}")] 31 | Spawn(#[from] std::io::Error), 32 | } 33 | 34 | pub fn scp, P: AsRef>( 35 | ip: T, 36 | port: u16, 37 | key: T, 38 | user: T, 39 | from: P, 40 | to: P, 41 | ) -> Result<(), ScpError> { 42 | let mut scp_cmd = Command::new("scp"); 43 | let from = from.as_ref().display().to_string(); 44 | let to = to.as_ref().display().to_string(); 45 | 46 | scp_cmd 47 | .args(vec!["-F", "/dev/null"]) 48 | .args(vec!["-o", "BatchMode=yes"]) 49 | .args(vec!["-o", "IdentitiesOnly=yes"]) 50 | .args(vec!["-o", "StrictHostKeyChecking=no"]) 51 | .args(vec!["-o", "UserKnownHostsFile=/dev/null"]) 52 | .args(vec!["-o", "ConnectTimeout=10s"]) 53 | .args(vec!["-i", key.as_ref()]) 54 | .args(vec!["-P", &format!("{}", port)]) 55 | .arg(&from) 56 | .arg(format!("{}@{}:{}", user.as_ref(), ip.as_ref(), to)); 57 | 58 | let output = scp_cmd.output()?; 59 | if !output.status.success() { 60 | let stderr = String::from_utf8(output.stderr).unwrap_or_default(); 61 | Err(ScpError::Scp(stderr)) 62 | } else { 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tools/model_manager/api/lang_model/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | from pathlib import Path 4 | 5 | def read_syscalls(filepath): 6 | with open(filepath, 'r') as f: 7 | syscalls = f.read().split() 8 | return syscalls 9 | 10 | def read_corpus(corpus_root, max_size=None): 11 | res = [] 12 | files = sorted(Path(corpus_root).iterdir(), key=os.path.getmtime, reverse=True) 13 | for i, filename in enumerate(files): 14 | if max_size and i > max_size: break 15 | filepath = os.path.join(corpus_root, filename) 16 | sent = [] 17 | with open(filepath, 'r') as f: 18 | for line in f.read().splitlines(): 19 | tokens = line.split(" = ") 20 | if len(tokens) !=2 and len(tokens) != 1: 21 | print(f'Exception: {line}, {tokens}') 22 | idx = tokens[-1].index('(') 23 | target = tokens[-1][:idx] 24 | sent.append(target) 25 | if len(sent) > 1: 26 | res.append(sent) 27 | return res 28 | 29 | class AvgrageMeter(object): 30 | 31 | def __init__(self): 32 | self.reset() 33 | 34 | def reset(self): 35 | self.avg = 0 36 | self.sum = 0 37 | self.cnt = 0 38 | 39 | def update(self, val, n=1): 40 | self.sum += val * n 41 | self.cnt += n 42 | self.avg = self.sum / self.cnt 43 | 44 | def accuracy(output, label, lengths, topk, device): 45 | maxk = max(topk) 46 | total = torch.sum(lengths).item() 47 | 48 | _, pred = output.topk(maxk, 1, True, True) 49 | pred = pred.t() 50 | 51 | pad_mask = torch.zeros(label.shape).to(device) 52 | pad_mask = ~pad_mask.eq(label) 53 | 54 | correct = pred.eq(label.view(1, -1).expand_as(pred)) 55 | correct = torch.logical_and(correct, pad_mask.view(1, -1).expand_as(pred)).contiguous() 56 | rtn = [] 57 | for k in topk: 58 | correct_k = correct[:k].view(-1).float().sum(0) 59 | rtn.append(correct_k.mul_(100.0 / total)) 60 | return rtn 61 | 62 | def adjust_learning_rate(optimizer, epoch, lr): 63 | lr = lr * (0.1 ** (epoch // 10)) 64 | for param_group in optimizer.param_groups: 65 | param_group['lr'] = lr 66 | -------------------------------------------------------------------------------- /healer_utils/healer_io/src/tokio.rs: -------------------------------------------------------------------------------- 1 | use crate::BackgroundIoHandle; 2 | use std::fs::File; 3 | use std::future::pending; 4 | use std::os::unix::io::{FromRawFd, IntoRawFd}; 5 | use std::sync::atomic::{AtomicBool, Ordering}; 6 | use std::sync::{Arc, Barrier, Mutex, Once}; 7 | use tokio::runtime::{Builder, Runtime}; 8 | 9 | static mut RUNTIME: Option = None; 10 | static ONCE: Once = Once::new(); 11 | 12 | fn init_runtime() { 13 | ONCE.call_once(|| { 14 | // from quaff-async. 15 | let rt = Builder::new_current_thread() 16 | .enable_all() 17 | .build() 18 | .expect("Failed to init tokio runtime."); 19 | unsafe { RUNTIME = Some(rt) } 20 | let barrier = Arc::new(Barrier::new(2)); 21 | let barrier1 = Arc::clone(&barrier); 22 | let task = async move { 23 | barrier1.wait(); 24 | pending::<()>().await 25 | }; 26 | std::thread::Builder::new() 27 | .name("healer-bg-thread".into()) 28 | .spawn(move || unsafe { RUNTIME.as_ref().unwrap() }.block_on(task)) 29 | .expect("failed to spawn healer background thread"); 30 | 31 | barrier.wait(); 32 | log::info!("background io initialized"); 33 | }); 34 | } 35 | 36 | pub fn runtime() -> &'static Runtime { 37 | init_runtime(); 38 | unsafe { RUNTIME.as_ref().unwrap() } 39 | } 40 | 41 | pub fn read_background(f: T, debug: bool) -> BackgroundIoHandle { 42 | let fd = f.into_raw_fd(); 43 | let f = unsafe { File::from_raw_fd(fd) }; 44 | let f = tokio::fs::File::from_std(f); 45 | let buf = Arc::new(Mutex::new(Vec::with_capacity(4096))); 46 | let finished = Arc::new(AtomicBool::new(false)); 47 | let buf1 = Arc::clone(&buf); 48 | let finished1 = Arc::clone(&finished); 49 | 50 | runtime().spawn(async move { 51 | use tokio::io::*; 52 | let mut buf = String::with_capacity(4096); 53 | let mut reader = BufReader::new(f); 54 | 55 | while let Ok(sz) = reader.readline(&mut buf).await { 56 | if sz == 0 { 57 | break; 58 | } 59 | let mut shared_buf = buf1.lock().unwrap(); 60 | shared_buf.extend(&buf[..sz]); 61 | if debug { 62 | print!("{}", &buf[..sz]); 63 | } 64 | buf.clear(); 65 | } 66 | 67 | finished1.store(true, Ordering::Relaxed); 68 | }); 69 | 70 | BackgroundIoHandle::new(buf, finished) 71 | } 72 | -------------------------------------------------------------------------------- /tools/model_manager/api/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import torch 4 | import torch.nn as nn 5 | import random 6 | import datetime 7 | from torch.utils.data import DataLoader 8 | import torch.optim as optim 9 | 10 | from django.http import HttpResponse 11 | from django.conf import settings 12 | from django.views.decorators.csrf import csrf_exempt 13 | 14 | from .lang_model.utils import read_corpus, read_syscalls 15 | from .lang_model.dataset import Vocab, SysDataset 16 | from .lang_model.model import RelationModel, train 17 | 18 | @csrf_exempt 19 | def model_train(request): 20 | if request.method == "POST": 21 | now_time = datetime.datetime.now() 22 | corpus_dir = request.POST.get("corpus", None) 23 | testcase_dir = request.POST.get("testcase", None) 24 | 25 | if not corpus_dir or not os.path.isdir(corpus_dir): 26 | return HttpResponse("error: invalid parameters") 27 | 28 | syscalls = read_syscalls(f"{settings.BASE_DIR}/api/lang_model/data/syscall_names") 29 | corpus = read_corpus(corpus_dir) 30 | 31 | # sample_size = int(0.6*len(corpus)) 32 | # testcase_sample = read_corpus(testcase_dir, sample_size) 33 | # corpus.extend(testcase_sample) 34 | 35 | vocab = Vocab() 36 | vocab.addDict(syscalls) 37 | vocab.addCorpus(corpus) 38 | sys_dataset = SysDataset(corpus, vocab) 39 | 40 | train_size = int(0.8*len(sys_dataset)) 41 | test_size = len(sys_dataset)-train_size 42 | train_dataset, test_dataset = torch.utils.data.random_split(sys_dataset, [train_size, test_size]) 43 | 44 | train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) 45 | test_loader = DataLoader(test_dataset, batch_size=64, shuffle=True) 46 | 47 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 48 | 49 | num_epoch = 50 50 | lr = 0.001 51 | embed_dim = 64 52 | hidden_dim = 128 53 | 54 | model = RelationModel(hidden_dim, embed_dim, len(vocab), device).to(device) 55 | optimizer = optim.Adam(model.parameters(), lr=lr) 56 | criterion = nn.CrossEntropyLoss() 57 | 58 | model_path = f"{settings.BASE_DIR}/api/lang_model/checkpoints" 59 | if not os.path.exists(model_path): 60 | os.mkdir(model_path) 61 | model_file = train(train_loader, test_loader, model, \ 62 | model_path, num_epoch, optimizer, \ 63 | lr, criterion, device) 64 | 65 | time_cost = datetime.datetime.now() - now_time 66 | print(f"model_update_time_cost: {time_cost}") 67 | 68 | return HttpResponse(model_file) 69 | 70 | -------------------------------------------------------------------------------- /healer_core/src/scheduler.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::atomic::{AtomicU64, Ordering}, 3 | }; 4 | 5 | use crate::{gen::choose_weighted, RngType}; 6 | // use healer_fuzzer::util; 7 | 8 | #[derive(Debug, Default)] 9 | pub struct Scheduler { 10 | // 0: static_relation, 1: model 11 | exec_total: [AtomicU64;2], 12 | intst_total: [AtomicU64;2], 13 | weights: [AtomicU64;2], 14 | } 15 | 16 | impl Scheduler { 17 | pub fn new() -> Self { 18 | Self { 19 | exec_total: [AtomicU64::new(0), AtomicU64::new(0)], 20 | intst_total: [AtomicU64::new(0), AtomicU64::new(0)], 21 | weights: [AtomicU64::new(1), AtomicU64::new(2)], 22 | } 23 | 24 | } 25 | pub fn inc_exec_total(&self, pos: usize) { 26 | self.exec_total[pos].fetch_add(1, Ordering::Relaxed); 27 | } 28 | 29 | pub fn inc_intst_total(&self, pos: usize) { 30 | self.intst_total[pos].fetch_add(1, Ordering::Relaxed); 31 | } 32 | 33 | pub fn reset(&self) { 34 | for i in 0..2 { 35 | self.exec_total[i].store(0, Ordering::Relaxed); 36 | self.intst_total[i].store(0, Ordering::Relaxed); 37 | self.weights[i].store(i as u64+1,Ordering::Relaxed); 38 | } 39 | } 40 | 41 | pub fn update_with_ucb(&self) { 42 | let exec_total: f64 = self.exec_total.iter().map(|x| x.load(Ordering::Relaxed)).sum::() as f64; 43 | let mut max_val = 0; 44 | let mut max_index = 0; 45 | for i in 0..2 { 46 | let cur_total= self.exec_total[i].load(Ordering::Relaxed) as f64; 47 | let cur_intst= self.intst_total[i].load(Ordering::Relaxed) as f64; 48 | let item1 = (1000.0*cur_intst/cur_total) as u64 ; 49 | let item2 = (1000.0*((2.0 * (exec_total+1.0).ln() / (cur_total+1.0)).sqrt())) as u64; 50 | if item1+item2 > max_val { 51 | max_val = item1+item2; 52 | max_index = i; 53 | } 54 | } 55 | self.weights[0].store(1-max_index as u64, Ordering::Relaxed); 56 | self.weights[1].store(1, Ordering::Relaxed); 57 | // if max_index == 0 { 58 | // self.weights[0].store(1, Ordering::Relaxed); 59 | // self.weights[1].store(1, Ordering::Relaxed); 60 | // } 61 | // else { 62 | // self.weights[0].store(0, Ordering::Relaxed); 63 | // self.weights[1].store(1, Ordering::Relaxed); 64 | // } 65 | } 66 | 67 | pub fn select(&self, rng: &mut RngType) -> usize { 68 | let weights = self.weights.iter().map(|w| w.load(Ordering::Relaxed)).collect::>(); 69 | let idx = choose_weighted(rng, &weights); 70 | idx 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MOCK 2 | 3 | We open source the prototype of Mock here. Mock is a Linux kernel fuzzer that can learn the contextual dependencies in syscall sequences and then generate context-aware test cases. In this way, Mock improves the input quality and explore deeper space of kernels. More details can be found in [our paper](https://www.ndss-symposium.org/ndss-paper/mock-optimizing-kernel-fuzzing-mutation-with-context-aware-dependency/) from NDSS'24. 4 | 5 | # Installation 6 | 7 | As Mock is built upon [Healer](https://github.com/SunHao-0/healer), please follow the [instructions](https://github.com/SunHao-0/healer/blob/main/README.md) to prepare necessary toolchains. Image and kernel preparation can be found in this [document](https://github.com/google/syzkaller/blob/master/docs/linux/setup_ubuntu-host_qemu-vm_x86-64-kernel.md). 8 | 9 | Besides, the training component is written in Python and interacts with the fuzzing component via http. Therefore, Python packages should be installed. 10 | ``` 11 | > pip3 install numpy django 12 | # if cuda is available 13 | > pip3 install torch torchvision torchaudio \ 14 | --index-url https://download.pytorch.org/whl/cu113 15 | ``` 16 | 17 | Once all the required tools have been installed, Mock can be easily built using the following command. It may some take to prepare the Rust bindings for Libtorch, [tch-rc](https://github.com/LaurentMazare/tch-rs), on which Mock depends. 18 | ``` 19 | > cargo build --release 20 | ``` 21 | You can find Mock and the patched Syzkaller binary (`syz-bin`) can be found in the `target/release` directory. 22 | 23 | # Usage 24 | 25 | The first step is run Mock is to launch a django service. It serves to monitor requests for the parallel model training. Start it using the following command: 26 | 27 | ``` 28 | > cd $MOCK_ROOT/tools/model_manager 29 | > python3 manage.py runserver 30 | ``` 31 | 32 | Suppose that everything is fine ([instructions](https://github.com/SunHao-0/healer/blob/main/README.md) here), execute following command in a new terminal to start the fuzzing (`-d` specifies the path to disk image, `-k` specifies the path to kernel image and `--ssh-key` specifies the path to ssh key). 33 | 34 | ``` 35 | > healer -d stretch.img --ssh-key stretch.id_rsa -k bzImage 36 | ``` 37 | 38 | # Citation 39 | ``` 40 | @inproceedings{ 41 | author = {Jiacheng, Xu and Xuhong, Zhang and Shouling, Ji and Yuan, Tian and Binbin, Zhao and Qinying, Wang and Peng, Cheng and Jiming, Chen}, 42 | title = {MOCK: Optimizing Kernel Fuzzing Mutation with Context-aware Dependency}, 43 | booktitle = {31st Annual Network and Distributed System Security Symposium, {NDSS} 2024, San Diego, California, USA, February 26 - March 1, 2024}, 44 | publisher = {The Internet Society}, 45 | year = {2024}, 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /tools/minimize/src/main.rs: -------------------------------------------------------------------------------- 1 | use healer_core::gen::{self, minimize}; 2 | use healer_core::lang_mod::model::ModelWrapper; 3 | use healer_core::parse::parse_prog; 4 | use healer_core::relation::{Relation, RelationWrapper}; 5 | use healer_core::scheduler::Scheduler; 6 | use healer_core::verbose::set_verbose; 7 | use rand::prelude::*; 8 | use rand::rngs::SmallRng; 9 | use std::fs::read_to_string; 10 | use std::io::{self}; 11 | use std::path::PathBuf; 12 | use std::process::exit; 13 | use structopt::StructOpt; 14 | use syz_wrapper::sys::load_target; 15 | 16 | #[derive(Debug, StructOpt)] 17 | struct Settings { 18 | /// Target to inspect. 19 | #[structopt(long, default_value = "linux/amd64")] 20 | target: String, 21 | /// Prog to mutate, randomly generate if not given. 22 | #[structopt(short, long)] 23 | prog: Option, 24 | /// Verbose. 25 | #[structopt(long)] 26 | verbose: bool, 27 | } 28 | 29 | fn main() { 30 | let settings = Settings::from_args(); 31 | env_logger::builder().format_timestamp_secs().init(); 32 | 33 | let target = load_target(&settings.target).unwrap_or_else(|e| { 34 | eprintln!("failed to load target: {}", e); 35 | exit(1) 36 | }); 37 | let relation = Relation::new(&target); 38 | let rw = RelationWrapper::new(relation); 39 | let mw = ModelWrapper::default(); 40 | let sc = Scheduler::default(); 41 | let mut rng = SmallRng::from_entropy(); 42 | set_verbose(settings.verbose); 43 | let mut p = if let Some(prog_file) = settings.prog.as_ref() { 44 | let p_str = read_to_string(prog_file).unwrap_or_else(|e| { 45 | eprintln!("failed to read '{}': {}", prog_file.display(), e); 46 | exit(1) 47 | }); 48 | parse_prog(&target, &p_str).unwrap_or_else(|e| { 49 | eprintln!("failed to parse: {} {}", prog_file.display(), e); 50 | exit(1) 51 | }) 52 | } else { 53 | gen::gen_prog(&target, &rw, &mw, &sc, &mut rng) 54 | }; 55 | println!( 56 | "> Prog to minimize, len {}:\n{}", 57 | p.calls().len(), 58 | p.display(&target) 59 | ); 60 | let idx = read_line().trim().parse::().unwrap_or_else(|e| { 61 | eprintln!("invalid input: {}", e); 62 | exit(1) 63 | }); 64 | 65 | minimize(&target, &mut p, idx, |p, idx| { 66 | println!("> current prog:\n{}", p.display(&target)); 67 | println!("current idx: {}", idx); 68 | read_line().to_ascii_lowercase().trim() == "y" 69 | }); 70 | 71 | println!("> prog after minimize:\n{}", p.display(&target)); 72 | exit(0); 73 | } 74 | 75 | fn read_line() -> String { 76 | println!(">>>"); 77 | let mut buf = String::new(); 78 | io::stdin().read_line(&mut buf).unwrap_or_else(|e| { 79 | eprintln!("failed to readline: {}", e); 80 | exit(1) 81 | }); 82 | buf 83 | } 84 | -------------------------------------------------------------------------------- /healer_core/src/lang_mod/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use tch::{Tensor, Device}; 4 | 5 | use crate::lang_mod::{utils::accuracy}; 6 | 7 | use super::model::ModelWrapper; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = 2 + 2; 12 | assert_eq!(result, 4); 13 | } 14 | 15 | #[test] 16 | fn test_accuracy() { 17 | let b = Tensor::of_slice(&[0,1,2]); 18 | let a: Tensor = Tensor::stack(&vec![Tensor::of_slice(&[0.3,0.5,0.2]), Tensor::of_slice(&[0.8,0.2,0.0]), Tensor::of_slice(&[0.1,0.2,0.7])], 0); 19 | let topk = vec![1,2]; 20 | let ret = accuracy(&a, &b, topk); 21 | assert_eq!(ret, vec![50.00, 100.00]); 22 | } 23 | 24 | #[test] 25 | fn test_train_model() { 26 | let model = ModelWrapper::default(); 27 | let corpus = String::from("/home/workdir/output/corpus"); 28 | let testcase = String::from("/home/workdir/output/corpus"); 29 | let model_file = model.train(&corpus, &testcase); 30 | println!("model_file: {}", model_file); 31 | assert_eq!(model_file, String::from("/home/model_manager/api/lang_model/checkpoints/syscall_model_jit_best.pt")); 32 | } 33 | 34 | #[test] 35 | fn test_load_model() { 36 | let model = ModelWrapper::default(); 37 | assert!(!model.exists()); 38 | let model_file = "/home/model_manager/api/lang_model/checkpoints/syscall_model_jit_best.pt"; 39 | model.load(model_file); 40 | assert!(model.exists()); 41 | } 42 | 43 | #[test] 44 | fn test_eval_model() { 45 | let mut device = Device::Cpu; 46 | if tch::Cuda::is_available() { 47 | device = Device::Cuda(0); 48 | } 49 | println!("{:?}", device); 50 | assert!(device.is_cuda()); 51 | let model = ModelWrapper::new("/home/model_manager/api/lang_model/checkpoints/syscall_model_jit_best.pt"); 52 | let out = model.eval(&[0,1,2]).unwrap().to(device); 53 | assert_eq!(out.size(), vec![3,4130]) 54 | } 55 | 56 | #[test] 57 | fn test_update_num() { 58 | let device = Device::cuda_if_available(); 59 | println!("{:?}", device); 60 | assert!(!device.is_cuda()); 61 | let model = ModelWrapper::new("/home/model_manager/api/lang_model/checkpoints/syscall_model_jit_best.pt"); 62 | assert_eq!(model.update_num(), 0); 63 | let model_file = "/home/model_manager/api/lang_model/checkpoints/syscall_model_jit_best.pt"; 64 | model.load(model_file); 65 | assert_eq!(model.update_num(), 1); 66 | } 67 | 68 | #[test] 69 | fn test_cuda() { 70 | let mut device = Device::Cpu; 71 | if tch::Cuda::is_available() { 72 | device = Device::Cuda(0); 73 | } 74 | println!("{:?}", device); 75 | assert!(device.is_cuda()); 76 | } 77 | 78 | } 79 | 80 | mod utils; 81 | pub mod model; 82 | pub mod mutate; 83 | -------------------------------------------------------------------------------- /healer_core/src/lang_mod/mutate.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::{collections::HashMap}; 3 | 4 | use rand::{prelude::SliceRandom}; 5 | use tch::Tensor; 6 | 7 | use crate::{ 8 | context::Context, 9 | RngType, 10 | syscall::SyscallId, 11 | select::select_with_calls, mutation::seq::select_call_to, 12 | }; 13 | 14 | pub fn select_call_to_wrapper(ctx: &mut Context, rng: &mut RngType, idx: usize) -> (SyscallId, usize) { 15 | type Selector = fn(&mut Context, &mut RngType, usize) -> SyscallId; 16 | const SELECTORS: [Selector; 2] = [select_call_to, select_call_to_lang]; 17 | let mut pos = 0; 18 | if ctx.model.exists() { 19 | pos = ctx.scheduler.select(rng); 20 | } 21 | (SELECTORS[pos](ctx, rng, idx), pos) 22 | } 23 | 24 | /// Select new call to location `idx`. 25 | pub fn select_call_to_lang(ctx: &mut Context, rng: &mut RngType, idx: usize) -> SyscallId { 26 | let model = ctx.model().inner.read().unwrap(); 27 | let calls = ctx.calls(); 28 | 29 | // first, consider calls that can be influenced by calls before `idx`. 30 | // as in NLP, syscall are labeled(word_id) from "3", 0->3, 1->4, 31 | // so it require to cast between sid and word_id 32 | let topk = 10; 33 | let mut prev_calls: Vec = calls[..idx].iter().map(|c| c.sid()+3).collect(); 34 | // "2" refer to Start-Of-Sentence(SOS) 35 | prev_calls.insert(0, 1); 36 | let prev_pred = model.eval(&prev_calls).unwrap(); 37 | let mut candidates = top_k(&prev_pred, topk); 38 | 39 | // then, consider calls that can be influence calls after `idx`. 40 | // cast between sid and word_id 41 | if idx != calls.len() { 42 | let mut back_calls: Vec = calls[idx..].iter().rev().map(|c| c.sid()+3).collect(); 43 | // "3" refer to End-of-Sentence(EOS) 44 | back_calls.insert(0, 2); 45 | let back_pred = model.eval(&back_calls).unwrap(); 46 | candidates.extend(top_k(&back_pred, topk)); 47 | } 48 | 49 | let candidates: Vec<(SyscallId, f64)> = candidates.into_iter().collect::>(); 50 | if let Ok(candidate) = candidates.choose_weighted(rng, |candidate| candidate.1) { 51 | if candidate.0 >= 3 { 52 | candidate.0-3 53 | } 54 | else { 55 | // failed to select with relation, use normal strategy. 56 | select_with_calls(ctx, rng) 57 | } 58 | } else { 59 | // failed to select with relation, use normal strategy. 60 | select_with_calls(ctx, rng) 61 | } 62 | } 63 | 64 | // generate `topk` candidates for given distribution 65 | pub fn top_k(pred: &Tensor, topk: i64) -> HashMap{ 66 | let (pred_val, pred_indexes) = pred.topk(topk, 1, true, true); 67 | let pred_val: Vec = Vec::from(pred_val); 68 | let pred_indexes: Vec = Vec::from(pred_indexes); 69 | let candidates: HashMap = pred_indexes.into_iter() 70 | .map(|i| i as SyscallId) 71 | .zip(pred_val.into_iter()) 72 | .collect(); 73 | candidates 74 | } 75 | -------------------------------------------------------------------------------- /syz_wrapper/patches/sysgen.diff: -------------------------------------------------------------------------------- 1 | --- ./sys/syz-sysgen/sysgen.go 2021-02-06 14:12:41.368275378 +0000 2 | +++ ./sys/syz-sysgen/sysgen.go 2021-02-06 14:12:03.618426639 +0000 3 | @@ -5,6 +5,7 @@ 4 | 5 | import ( 6 | "bytes" 7 | + "encoding/json" 8 | "flag" 9 | "fmt" 10 | "io" 11 | @@ -58,6 +59,8 @@ 12 | 13 | var srcDir = flag.String("src", "", "path to root of syzkaller source dir") 14 | var outDir = flag.String("out", "", "path to out dir") 15 | +var genJson = flag.Bool("gen_json", true, "generate json representation") 16 | +var jsonOutDir = flag.String("json_out", "json", "path to json representation") 17 | 18 | func main() { 19 | defer tool.Init()() 20 | @@ -68,6 +71,10 @@ 21 | } 22 | sort.Strings(OSList) 23 | 24 | + if *genJson { 25 | + osutil.MkdirAll(filepath.Join(*outDir, "sys", *jsonOutDir)) 26 | + } 27 | + 28 | data := &ExecutorData{} 29 | for _, OS := range OSList { 30 | descriptions := ast.ParseGlob(filepath.Join(*srcDir, "sys", OS, "*.txt"), nil) 31 | @@ -79,6 +86,9 @@ 32 | os.Exit(1) 33 | } 34 | osutil.MkdirAll(filepath.Join(*outDir, "sys", OS, "gen")) 35 | + if *genJson { 36 | + osutil.MkdirAll(filepath.Join(*outDir, "sys", *jsonOutDir, OS)) 37 | + } 38 | 39 | var archs []string 40 | for arch := range targets.List[OS] { 41 | @@ -140,6 +150,10 @@ 42 | rev := hash.String(out.Bytes()) 43 | fmt.Fprintf(out, "const revision_%v = %q\n", job.Target.Arch, rev) 44 | writeSource(sysFile, out.Bytes()) 45 | + // generate json representation. 46 | + if *genJson { 47 | + generate_json(job.Target, prog, consts, rev) 48 | + } 49 | 50 | job.ArchData = generateExecutorSyscalls(job.Target, prog.Syscalls, rev) 51 | 52 | @@ -186,6 +200,41 @@ 53 | writeExecutorSyscalls(data) 54 | } 55 | 56 | +func generate_json(target *targets.Target, prg *compiler.Prog, consts map[string]uint64, rev string) { 57 | + type Type struct { 58 | + Name string 59 | + Value prog.Type 60 | + } 61 | + types := make([]Type, 0, len(prg.Types)) 62 | + for _, ty := range prg.Types { 63 | + types = append(types, Type{Name: reflect.TypeOf(ty).Elem().Name(), Value: ty}) 64 | + } 65 | + type Sys struct { 66 | + Target *targets.Target 67 | + Syscalls []*prog.Syscall 68 | + Types []Type 69 | + Resources []*prog.ResourceDesc 70 | + Consts map[string]uint64 71 | + Revision string 72 | + } 73 | + sys := Sys{ 74 | + Target: target, 75 | + Syscalls: prg.Syscalls, 76 | + Types: types, 77 | + Resources: prg.Resources, 78 | + Consts: consts, 79 | + Revision: rev, 80 | + } 81 | + jsonFile := filepath.Join(*outDir, "sys", *jsonOutDir, target.OS, target.Arch+".json") 82 | + sys_json, err := json.Marshal(sys) 83 | + if err != nil { 84 | + fmt.Printf("failed to marshal target %v/%v: %v\n", target.OS, target.Arch, err) 85 | + os.Exit(1) 86 | + } 87 | + writeSource(jsonFile, sys_json) 88 | + 89 | +} 90 | + 91 | func generate(target *targets.Target, prg *compiler.Prog, consts map[string]uint64, out io.Writer) { 92 | tag := fmt.Sprintf("syz_target,syz_os_%v,syz_arch_%v", target.OS, target.Arch) 93 | if target.VMArch != "" { 94 | -------------------------------------------------------------------------------- /healer_core/src/mutation/group.rs: -------------------------------------------------------------------------------- 1 | //! Mutate value of `array`, `struct`, `union` type. 2 | use super::call::contains_out_res; 3 | use crate::{context::Context, gen::gen_ty_value, ty::ArrayType, value::Value, RngType}; 4 | use rand::prelude::*; 5 | 6 | #[allow(clippy::comparison_chain)] 7 | pub fn mutate_array(ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 8 | let ty = val.ty(ctx.target).checked_as_array(); 9 | let val = val.checked_as_group_mut(); 10 | let old_len = val.inner.len(); 11 | let new_len = mutate_array_len(rng, ty, old_len); 12 | let mut shuffled = false; 13 | 14 | if new_len > old_len { 15 | let new_vals = (0..new_len - old_len) 16 | .map(|_| gen_ty_value(ctx, rng, ty.elem(), val.dir())) 17 | .collect::>(); 18 | val.inner.extend(new_vals); 19 | } else if new_len < old_len { 20 | if val.inner.iter().all(|v| !contains_out_res(v)) || rng.gen_ratio(1, 20) { 21 | val.inner.drain(new_len..); 22 | } 23 | } else { 24 | val.inner.shuffle(rng); 25 | shuffled = true; 26 | } 27 | debug_info!("mutate_array: {} -> {}", old_len, val.inner.len()); 28 | 29 | old_len != val.inner.len() || shuffled 30 | } 31 | 32 | fn mutate_array_len(rng: &mut RngType, ty: &ArrayType, old_len: usize) -> usize { 33 | let mut new_len = old_len; 34 | if let Some(r) = ty.range() { 35 | let mut changed = false; 36 | while *r.start() != *r.end() && !changed { 37 | new_len = rng.gen_range(r.clone()) as usize; 38 | changed = new_len != old_len; 39 | } 40 | } else if rng.gen() { 41 | while new_len == old_len || rng.gen() { 42 | new_len += 1; 43 | } 44 | } else { 45 | new_len = rng.gen_range(0..=10) 46 | }; 47 | new_len 48 | } 49 | 50 | pub fn mutate_struct(_ctx: &mut Context, _rng: &mut RngType, _val: &mut Value) -> bool { 51 | debug_info!("mutate_struct: doing nothing"); 52 | false 53 | } 54 | 55 | pub fn mutate_union(ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 56 | let union_val = val.checked_as_union_mut(); 57 | let ty = union_val.ty(ctx.target).checked_as_union(); 58 | 59 | if ty.fields().len() <= 1 { 60 | debug_info!("mutate_union: fields too short, skip"); 61 | return false; 62 | } 63 | 64 | if contains_out_res(&union_val.option) && rng.gen_ratio(19, 20) { 65 | debug_info!("mutate_union: contains output res, skip"); 66 | return false; 67 | } 68 | 69 | let old_index = union_val.index as usize; 70 | let mut new_index = old_index; 71 | while new_index == old_index { 72 | new_index = rng.gen_range(0..ty.fields().len()); 73 | } 74 | let f = &ty.fields()[new_index]; 75 | let dir = f.dir().unwrap_or_else(|| union_val.option.dir()); 76 | let new_val = gen_ty_value(ctx, rng, f.ty(), dir); 77 | union_val.option = Box::new(new_val); 78 | union_val.index = new_index as u64; 79 | debug_info!("mutate_union: index {} -> {}", old_index, new_index); 80 | 81 | true 82 | } 83 | -------------------------------------------------------------------------------- /tools/model_manager/api/lang_model/dataset.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | from torch.utils.data import Dataset 3 | import torch 4 | import numpy as np 5 | # import h5py 6 | 7 | class Vocab: 8 | def __init__(self): 9 | self.idx2word = ["PAD","SOS","EOS"] 10 | self.word2idx = {"PAD":0,"SOS":1,"EOS":2} 11 | self.word2count = {"PAD":1,"SOS":1,"EOS":1} 12 | self.num_words = 3 13 | 14 | def addCorpus(self, corpus): 15 | for sent in corpus: 16 | for word in sent: 17 | self.addWord(word) 18 | 19 | def addDict(self, dictionary): 20 | for word in dictionary: 21 | if word not in self.word2idx: 22 | self.word2idx[word] = self.num_words 23 | self.word2count[word] = 0 24 | self.idx2word.append(word) 25 | self.num_words += 1 26 | 27 | def addWord(self, word): 28 | if word not in self.word2idx: 29 | self.word2idx[word] = self.num_words 30 | self.word2count[word] = 1 31 | self.idx2word.append(word) 32 | self.num_words += 1 33 | else: 34 | self.word2count[word] += 1 35 | 36 | def __len__(self): 37 | return self.num_words 38 | 39 | 40 | class SysDataset(Dataset): 41 | def __init__(self, corpus=None, vocab=None, max_len=20): 42 | self.vocab = vocab 43 | self.max_len = max_len 44 | self.seq_len = [] 45 | self.corpus = [] 46 | if corpus and vocab: 47 | self.corpus = np.array([self._sent2data(sent) for sent in corpus], dtype=np.int) 48 | self.seq_len = np.array(self.seq_len, dtype=np.int) 49 | 50 | 51 | def _sent2data(self, sent): 52 | self.seq_len.append(min(len(sent), self.max_len)) 53 | if len(sent) > self.max_len: 54 | sent = ["SOS"]+sent[:self.max_len]+["EOS"] 55 | else: 56 | sent = ["SOS"]+sent+['PAD' for _ in range(self.max_len-len(sent))]+["EOS"] 57 | data = [self.vocab.word2idx[word] for word in sent] 58 | return data 59 | 60 | def __getitem__(self, idx): 61 | return (torch.tensor(self.corpus[idx][:-1], dtype=torch.long), 62 | torch.tensor(self.corpus[idx][1:], dtype=torch.long), 63 | self.seq_len[idx]+1) 64 | 65 | def __len__(self): 66 | return len(self.corpus) 67 | 68 | def save(self, corpus_file, vocab_file): 69 | with h5py.File(corpus_file, 'w') as f: 70 | f['corpus'] = self.corpus 71 | f['seq_len'] = self.seq_len 72 | 73 | with open(vocab_file, 'wb') as f: 74 | pickle.dump(obj=self.vocab.word2idx, file=f) 75 | 76 | def load(self, corpus_file, vocab_file): 77 | with h5py.File(corpus_file, 'r') as f: 78 | self.corpus = f['corpus'].value 79 | self.seq_len = f['seq_len'].value 80 | self.max_len = self.corpus.shape[1] 81 | 82 | self.vocab = Vocab() 83 | with open(vocab_file, 'rb') as f: 84 | self.vocab.word2idx = pickle.load(file=f) 85 | items = sorted(self.vocab.word2idx.items(), key=lambda x: x[1]) 86 | self.vocab.idx2word = [item[0] for item in items] 87 | self.vocab.num_words = len(self.vocab.idx2word) -------------------------------------------------------------------------------- /tools/mutate_prog/src/main.rs: -------------------------------------------------------------------------------- 1 | use healer_core::corpus::CorpusWrapper; 2 | use healer_core::gen::{self, set_prog_len_range, FAVORED_MAX_PROG_LEN, FAVORED_MIN_PROG_LEN}; 3 | use healer_core::lang_mod::model::ModelWrapper; 4 | use healer_core::mutation::mutate; 5 | use healer_core::parse::parse_prog; 6 | use healer_core::relation::{Relation, RelationWrapper}; 7 | use healer_core::scheduler::Scheduler; 8 | use healer_core::target::Target; 9 | use healer_core::verbose::set_verbose; 10 | use rand::prelude::*; 11 | use rand::rngs::SmallRng; 12 | use std::fs::read_to_string; 13 | use std::path::PathBuf; 14 | use std::process::exit; 15 | use structopt::StructOpt; 16 | use syz_wrapper::sys::load_target; 17 | 18 | #[derive(Debug, StructOpt)] 19 | struct Settings { 20 | /// Target to inspect. 21 | #[structopt(long, default_value = "linux/amd64")] 22 | target: String, 23 | /// Verbose. 24 | #[structopt(long)] 25 | verbose: bool, 26 | /// Mutate `n` time. 27 | #[structopt(long, short, default_value = "1")] 28 | n: usize, 29 | /// Prog to mutate, randomly generate if not given. 30 | #[structopt(short, long)] 31 | prog: Option, 32 | } 33 | 34 | fn main() { 35 | let settings = Settings::from_args(); 36 | env_logger::builder().format_timestamp_secs().init(); 37 | 38 | let target = load_target(&settings.target).unwrap_or_else(|e| { 39 | eprintln!("failed to load target: {}", e); 40 | exit(1) 41 | }); 42 | let relation = Relation::new(&target); 43 | let rw = RelationWrapper::new(relation); 44 | let mw = ModelWrapper::default(); 45 | let sc = Scheduler::default(); 46 | let mut rng = SmallRng::from_entropy(); 47 | let mut p = if let Some(prog_file) = settings.prog.as_ref() { 48 | let p_str = read_to_string(prog_file).unwrap_or_else(|e| { 49 | eprintln!("failed to read '{}': {}", prog_file.display(), e); 50 | exit(1) 51 | }); 52 | parse_prog(&target, &p_str).unwrap_or_else(|e| { 53 | eprintln!("failed to parse: {} {}", prog_file.display(), e); 54 | exit(1) 55 | }) 56 | } else { 57 | gen::gen_prog(&target, &rw, &mw, &sc, &mut rng) 58 | }; 59 | println!("mutating following prog:\n{}", p.display(&target)); 60 | let corpus = dummy_corpus(&target, &rw, &mw, &sc, &mut rng); 61 | println!("corpus len: {}", corpus.len()); 62 | set_verbose(settings.verbose); 63 | for _ in 0..settings.n { 64 | let (mutated, _op) = mutate(&target, &rw, &mw, &corpus, &sc, &mut rng, &mut p); 65 | if mutated { 66 | println!("mutated prog:\n{}", p.display(&target)); 67 | } 68 | } 69 | exit(0); // no need to drop mem 70 | } 71 | 72 | fn dummy_corpus(target: &Target, relation: &RelationWrapper, model: &ModelWrapper, 73 | scheduler: &Scheduler, rng: &mut SmallRng) -> CorpusWrapper { 74 | let corpus = CorpusWrapper::new(); 75 | let n = rng.gen_range(8..=32); 76 | set_prog_len_range(3..8); // progs in corpus are always shorter 77 | for _ in 0..n { 78 | let prio = rng.gen_range(64..=1024); 79 | corpus.add_prog(gen::gen_prog(target, relation, model, scheduler, rng), prio); 80 | } 81 | set_prog_len_range(FAVORED_MIN_PROG_LEN..FAVORED_MAX_PROG_LEN); // restore 82 | corpus 83 | } 84 | -------------------------------------------------------------------------------- /healer_core/src/gen/ptr.rs: -------------------------------------------------------------------------------- 1 | //! Generate value for `ptr`, `vma` type. 2 | use crate::target::Target; 3 | use crate::value::VmaValue; 4 | use crate::HashMap; 5 | use crate::{ 6 | context::Context, 7 | gen::gen_ty_value, 8 | ty::{Dir, Type, TypeKind}, 9 | value::{PtrValue, Value}, 10 | RngType, 11 | }; 12 | use rand::prelude::*; 13 | use std::cell::RefCell; 14 | use std::ops::RangeInclusive; 15 | 16 | thread_local! { 17 | static REC_DEPTH: RefCell> = RefCell::new(HashMap::new()); 18 | } 19 | 20 | #[inline] 21 | fn inc_rec_of(k: &str) { 22 | REC_DEPTH.with(|rec| { 23 | let mut rec = rec.borrow_mut(); 24 | if !rec.contains_key(k) { 25 | rec.insert(k.to_string(), 0); 26 | } 27 | *rec.get_mut(k).unwrap() += 1; 28 | }) 29 | } 30 | 31 | #[inline] 32 | fn dec_rec_of(k: &str) { 33 | REC_DEPTH.with(|rec| { 34 | let mut rec = rec.borrow_mut(); 35 | if let Some(v) = rec.get_mut(k) { 36 | *v -= 1; 37 | if *v == 0 { 38 | rec.remove(k); 39 | } 40 | } 41 | }) 42 | } 43 | 44 | #[inline] 45 | fn rec_depth_of(k: &str) -> usize { 46 | REC_DEPTH.with(|rec| { 47 | let rec = rec.borrow(); 48 | rec.get(k).copied().unwrap_or(0) 49 | }) 50 | } 51 | 52 | pub fn gen_ptr(ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 53 | use TypeKind::*; 54 | 55 | let ptr_ty = ty.checked_as_ptr(); 56 | let target = ctx.target(); 57 | let elem_ty = target.ty_of(ptr_ty.elem()); 58 | let mut rec: Option<&str> = None; 59 | 60 | if ptr_ty.optional() && matches!(elem_ty.kind(), Struct | Union | Array) { 61 | if rec_depth_of(elem_ty.name()) >= 2 { 62 | return PtrValue::new_special(ty.id(), dir, 0).into(); 63 | } 64 | rec = Some(elem_ty.name()); 65 | inc_rec_of(elem_ty.name()); 66 | } 67 | 68 | let val = if !target.special_ptrs().is_empty() && rng.gen_ratio(1, 1000) { 69 | let index = rng.gen_range(0..target.special_ptrs().len()); 70 | PtrValue::new_special(ptr_ty.id(), dir, index as u64) 71 | } else { 72 | let elem_val = gen_ty_value(ctx, rng, elem_ty, ptr_ty.dir()); 73 | let addr = ctx.mem_allocator().alloc(elem_val.layout(target)); 74 | PtrValue::new(ptr_ty.id(), dir, addr, elem_val) 75 | }; 76 | if let Some(rec) = rec { 77 | dec_rec_of(rec); 78 | } 79 | val.into() 80 | } 81 | 82 | pub fn gen_vma(ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 83 | let ty = ty.checked_as_vma(); 84 | let page_range = ty 85 | .range() 86 | .unwrap_or_else(|| rand_page_range(ctx.target(), rng)); 87 | let page_num = rng.gen_range(page_range); 88 | let page = ctx.vma_allocator().alloc(rng, page_num); 89 | VmaValue::new( 90 | ty.id(), 91 | dir, 92 | page * ctx.target().page_sz(), 93 | page_num * ctx.target().page_sz(), 94 | ) 95 | .into() 96 | } 97 | 98 | fn rand_page_range(target: &Target, rng: &mut RngType) -> RangeInclusive { 99 | match rng.gen_range(0..100) { 100 | 0..=89 => 1..=4, 101 | 90..=98 => 1..=4, 102 | _ => 1..=(target.page_num() / 4 * 3), 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /healer_core/src/lang_mod/model.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::RwLock, collections::HashMap, time::Duration}; 2 | use reqwest; 3 | use tch::{CModule, Tensor, Device}; 4 | 5 | #[derive(Default, Debug)] 6 | pub struct ModelWrapper { 7 | pub inner: RwLock, 8 | } 9 | 10 | impl ModelWrapper { 11 | pub fn new >(path: P) -> Self { 12 | Self { 13 | inner: RwLock::new(Model::new(path)) 14 | } 15 | } 16 | 17 | #[inline] 18 | pub fn load>(&self, path: P) { 19 | let mut inner = self.inner.write().unwrap(); 20 | inner.load(path); 21 | } 22 | 23 | pub fn train(&self, corpus: &String, testcase: &String) -> String { 24 | let mut params = HashMap::new(); 25 | params.insert("corpus", corpus.clone()); 26 | params.insert("testcase", testcase.clone()); 27 | 28 | let client = reqwest::blocking::Client::new(); 29 | let res = client.post("http://127.0.0.1:8000/api/model") 30 | .timeout(Duration::from_secs(60*60)) 31 | .form(¶ms).send().unwrap(); 32 | let model_file = res.text().unwrap(); 33 | model_file 34 | } 35 | 36 | #[inline] 37 | pub fn eval(&self, inputs: &[usize]) -> Option { 38 | let inner = self.inner.read().unwrap(); 39 | inner.eval(inputs) 40 | } 41 | 42 | #[inline] 43 | pub fn exists(&self) -> bool { 44 | let inner = self.inner.read().unwrap(); 45 | !inner.model.is_none() 46 | } 47 | 48 | #[inline] 49 | pub fn update_num(&self) -> u32 { 50 | let inner = self.inner.read().unwrap(); 51 | inner.update_num() 52 | } 53 | } 54 | 55 | #[derive(Default, Debug)] 56 | pub struct Model { 57 | model: Option, 58 | device: Option, 59 | update_num: u32, 60 | } 61 | 62 | impl Model { 63 | pub fn new>(path: P) -> Self { 64 | let mut device = Device::Cpu; 65 | if tch::Cuda::is_available() { 66 | device = Device::Cuda(0); 67 | } 68 | Self { 69 | model: Some(tch::CModule::load_on_device(path, device).unwrap()), 70 | device: Some(device), 71 | update_num: 0, 72 | } 73 | } 74 | 75 | pub fn load>(&mut self, path: P) { 76 | let mut device = Device::Cpu; 77 | if tch::Cuda::is_available() { 78 | device = Device::Cuda(0); 79 | } 80 | self.device = Some(device); 81 | self.model = Some(tch::CModule::load_on_device(path, device).unwrap()); 82 | self.update_num = self.update_num + 1; 83 | } 84 | 85 | pub fn eval(&self, inputs: &[usize]) -> Option { 86 | if self.model.is_none() { 87 | None 88 | } 89 | else { 90 | let length = Tensor::of_slice(&[inputs.len() as u8]).to_device(self.device.unwrap()); 91 | let inputs_i32 = inputs.into_iter().map(|i| *i as i32).collect::>(); 92 | let inputs_i32 = Tensor::stack(&[Tensor::of_slice(&inputs_i32)], 0).to_device(self.device.unwrap()); 93 | let outputs = self.model.as_ref().unwrap().forward_ts(&[inputs_i32, length]).unwrap().softmax(-1, tch::Kind::Float); 94 | Some(outputs) 95 | } 96 | } 97 | 98 | #[inline] 99 | pub fn update_num(&self) -> u32 { 100 | self.update_num 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /syz_wrapper/patches/ivshm_setup.h: -------------------------------------------------------------------------------- 1 | #ifndef HEALER_IVSHM_SETUP 2 | #define HEALER_IVSHM_SETUP 3 | 4 | #if GOOS_linux 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define IVSHMEM_PCI_VENDOR_ID 0x1af4 14 | #define IVSHMEM_PCI_DEVICE_ID 0x1110 15 | #define PCI_SYSFS_PATH "/sys/bus/pci/devices" 16 | static int in_fd_inner = -1, out_fd_inner = -1; 17 | 18 | static char* read_str(char* f) 19 | { 20 | static char buf[256]; 21 | int fd, n; 22 | fd = open(f, O_RDONLY); 23 | if (fd < 0) { 24 | return NULL; 25 | } 26 | n = read(fd, buf, 256); 27 | close(fd); 28 | if (n < 0 || n >= 256) { 29 | return NULL; 30 | } 31 | buf[n] = 0; 32 | return buf; 33 | } 34 | 35 | static long read_val(char* f) 36 | { 37 | char* str; 38 | long val = -1; 39 | str = read_str(f); 40 | if (str) { 41 | val = strtol(str, NULL, 0); 42 | } 43 | return val; 44 | } 45 | 46 | static long get_resource2_sz(char* fname) 47 | { 48 | char buf[256]; 49 | FILE* f; 50 | unsigned long long start, end, size = -1, flags; 51 | if ((f = fopen(fname, "r"))) { 52 | // skip 0,1 53 | if (fgets(buf, 256, f) && fgets(buf, 256, f)) { 54 | if (fgets(buf, 256, f)) { 55 | if (sscanf(buf, "%llx %llx %llx", &start, &end, &flags) == 3) { 56 | if (end > start) 57 | size = end - start + 1; 58 | } 59 | } 60 | } 61 | fclose(f); 62 | } 63 | return size; 64 | } 65 | 66 | static void scan_pci_device() 67 | { 68 | DIR* pci_dir; 69 | struct dirent* entry; 70 | char dir_name[FILENAME_MAX]; 71 | 72 | pci_dir = opendir(PCI_SYSFS_PATH); 73 | if (!pci_dir) { 74 | failmsg("failed to open", "%s", PCI_SYSFS_PATH); 75 | } 76 | while ((entry = readdir(pci_dir))) { 77 | long vendor, device, region_sz; 78 | int fd; 79 | // skip ".", "..", or other special device. 80 | if (entry->d_name[0] == '.') 81 | continue; 82 | 83 | snprintf(dir_name, FILENAME_MAX, "%s/%s/vendor", PCI_SYSFS_PATH, entry->d_name); 84 | vendor = read_val(dir_name); 85 | snprintf(dir_name, FILENAME_MAX, "%s/%s/device", PCI_SYSFS_PATH, entry->d_name); 86 | device = read_val(dir_name); 87 | 88 | if (vendor == IVSHMEM_PCI_VENDOR_ID && device == IVSHMEM_PCI_DEVICE_ID) { 89 | snprintf(dir_name, FILENAME_MAX, "%s/%s/resource", PCI_SYSFS_PATH, entry->d_name); 90 | region_sz = get_resource2_sz(dir_name); 91 | snprintf(dir_name, FILENAME_MAX, "%s/%s/resource2", PCI_SYSFS_PATH, entry->d_name); 92 | fd = open(dir_name, O_RDWR); 93 | if (region_sz == kMaxOutput) { 94 | out_fd_inner = fd; 95 | } else if (region_sz == kMaxInput) { 96 | in_fd_inner = fd; 97 | } else { 98 | failmsg("unexpect ivshm size:", "%ld", region_sz); 99 | } 100 | } 101 | } 102 | closedir(pci_dir); 103 | } 104 | 105 | static void ivshm_setup(int in_fd, int out_fd) 106 | { 107 | scan_pci_device(); 108 | if (in_fd_inner == -1 || out_fd_inner == -1) { 109 | fail("failed to setup ivshm"); 110 | } 111 | if (dup2(in_fd_inner, in_fd) < 0) { 112 | failmsg("failed to dup:", "%d -> %d.", in_fd_inner, in_fd); 113 | } 114 | if (dup2(out_fd_inner, out_fd) < 0) { 115 | failmsg("failed to dup:", "%d -> %d.", in_fd_inner, in_fd); 116 | } 117 | } 118 | 119 | #define IVSHM_SETUP_SNIPPET \ 120 | do { \ 121 | if (argc >= 2 && strcmp(argv[1], "use-ivshm") == 0) { \ 122 | ivshm_setup(kInFd, kOutFd); \ 123 | } \ 124 | } while (0) 125 | 126 | #else 127 | #error Currently, ivshm_setup only supports linux. 128 | #endif 129 | 130 | #endif // HEALER_IVSHM_SETUP 131 | -------------------------------------------------------------------------------- /tools/model_manager/model_manager/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for model_manager project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.0.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.0/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'django-insecure-rg8i_!0mao4zdq)2b9e%k1k^=xtg83s$&kv!spsnqsf-j_!=65' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | ] 41 | 42 | MIDDLEWARE = [ 43 | 'django.middleware.security.SecurityMiddleware', 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.messages.middleware.MessageMiddleware', 49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 50 | ] 51 | 52 | ROOT_URLCONF = 'model_manager.urls' 53 | 54 | TEMPLATES = [ 55 | { 56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 57 | 'DIRS': [], 58 | 'APP_DIRS': True, 59 | 'OPTIONS': { 60 | 'context_processors': [ 61 | 'django.template.context_processors.debug', 62 | 'django.template.context_processors.request', 63 | 'django.contrib.auth.context_processors.auth', 64 | 'django.contrib.messages.context_processors.messages', 65 | ], 66 | }, 67 | }, 68 | ] 69 | 70 | WSGI_APPLICATION = 'model_manager.wsgi.application' 71 | 72 | 73 | # Database 74 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases 75 | 76 | DATABASES = { 77 | 'default': { 78 | 'ENGINE': 'django.db.backends.sqlite3', 79 | 'NAME': BASE_DIR / 'db.sqlite3', 80 | } 81 | } 82 | 83 | 84 | # Password validation 85 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators 86 | 87 | AUTH_PASSWORD_VALIDATORS = [ 88 | { 89 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 90 | }, 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 99 | }, 100 | ] 101 | 102 | 103 | # Internationalization 104 | # https://docs.djangoproject.com/en/4.0/topics/i18n/ 105 | 106 | LANGUAGE_CODE = 'en-us' 107 | 108 | TIME_ZONE = 'UTC' 109 | 110 | USE_I18N = True 111 | 112 | USE_TZ = True 113 | 114 | 115 | # Static files (CSS, JavaScript, Images) 116 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ 117 | 118 | STATIC_URL = 'static/' 119 | 120 | # Default primary key field type 121 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field 122 | 123 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 124 | -------------------------------------------------------------------------------- /healer_core/src/ty/res.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::{ 4 | ty::{common::CommonInfo, BinaryFormat, Dir}, 5 | value::{ResValue, ResValueKind, Value}, 6 | }; 7 | 8 | pub type ResKind = Box; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct ResType { 12 | comm: CommonInfo, 13 | bin_fmt: BinaryFormat, 14 | /// Name of resource. 15 | name: Box, 16 | /// Subkind of these kind resource. 17 | kinds: Box<[ResKind]>, 18 | /// Special value for current resource. 19 | vals: Box<[u64]>, 20 | } 21 | 22 | impl ResType { 23 | common_attr_getter! {} 24 | 25 | default_int_format_attr_getter! {} 26 | 27 | #[inline(always)] 28 | pub fn format(&self) -> BinaryFormat { 29 | self.bin_fmt 30 | } 31 | 32 | #[inline(always)] 33 | pub fn res_name(&self) -> &ResKind { 34 | &self.name 35 | } 36 | 37 | #[inline(always)] 38 | pub fn kinds(&self) -> &[Box] { 39 | &self.kinds 40 | } 41 | 42 | #[inline(always)] 43 | pub fn special_vals(&self) -> &[u64] { 44 | &self.vals 45 | } 46 | 47 | pub fn default_special_val(&self) -> u64 { 48 | self.vals.get(0).copied().unwrap_or_default() 49 | } 50 | 51 | pub fn default_value(&self, dir: Dir) -> Value { 52 | ResValue::new_null(self.id(), dir, self.default_special_val()).into() 53 | } 54 | 55 | pub fn is_default(&self, val: &Value) -> bool { 56 | let val = val.checked_as_res(); 57 | 58 | if let ResValueKind::Null = &val.kind { 59 | val.val == self.default_special_val() && val.op_add == 0 && val.op_div == 0 60 | } else { 61 | false 62 | } 63 | } 64 | 65 | pub fn is_subtype_of(&self, other: &ResType) -> bool { 66 | if other.kinds.len() > self.kinds.len() { 67 | return false; 68 | } 69 | let kinds = &self.kinds[..other.kinds.len()]; 70 | kinds == &other.kinds[..] 71 | } 72 | } 73 | 74 | impl Display for ResType { 75 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 76 | write!(f, "resource[{}]", self.name) 77 | } 78 | } 79 | 80 | eq_ord_hash_impl!(ResType); 81 | 82 | #[derive(Debug, Clone)] 83 | pub struct ResTypeBuilder { 84 | comm: CommonInfo, 85 | bin_fmt: BinaryFormat, 86 | name: String, 87 | kinds: Vec, 88 | vals: Vec, 89 | } 90 | 91 | impl ResTypeBuilder { 92 | pub fn new(comm: CommonInfo) -> Self { 93 | Self { 94 | comm, 95 | name: String::new(), 96 | bin_fmt: BinaryFormat::Native, 97 | kinds: Vec::new(), 98 | vals: Vec::new(), 99 | } 100 | } 101 | 102 | pub fn comm(&mut self, comm: CommonInfo) -> &mut Self { 103 | self.comm = comm; 104 | self 105 | } 106 | 107 | pub fn bin_fmt(&mut self, fmt: BinaryFormat) -> &mut Self { 108 | self.bin_fmt = fmt; 109 | self 110 | } 111 | 112 | pub fn res_name>(&mut self, name: T) -> &mut Self { 113 | self.name = name.into(); 114 | self 115 | } 116 | 117 | pub fn kinds(&mut self, kinds: Vec) -> &mut Self { 118 | self.kinds = kinds; 119 | self 120 | } 121 | 122 | pub fn vals(&mut self, vals: Vec) -> &mut Self { 123 | self.vals = vals; 124 | self 125 | } 126 | 127 | pub fn build(self) -> ResType { 128 | ResType { 129 | comm: self.comm, 130 | name: self.name.into_boxed_str(), 131 | bin_fmt: self.bin_fmt, 132 | kinds: self 133 | .kinds 134 | .into_iter() 135 | .map(|kind| kind.into_boxed_str()) 136 | .collect(), 137 | vals: self.vals.into_boxed_slice(), 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /healer_fuzzer/src/stats.rs: -------------------------------------------------------------------------------- 1 | use crate::util::stop_soon; 2 | use anyhow::{Context, Result}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fs::{File, OpenOptions}; 5 | use std::path::Path; 6 | use std::thread::sleep; 7 | use std::{ 8 | sync::atomic::{AtomicU64, Ordering}, 9 | time::Duration, 10 | }; 11 | 12 | #[derive(Debug, Default, Serialize, Deserialize)] 13 | pub(crate) struct Stats { 14 | fuzzing: AtomicU64, 15 | repro: AtomicU64, 16 | relations: AtomicU64, 17 | crashes: AtomicU64, 18 | unique_crash: AtomicU64, 19 | // crash_suppressed: AtomicU64, 20 | vm_restarts: AtomicU64, 21 | corpus_size: AtomicU64, 22 | exec_total: AtomicU64, 23 | cal_cov: AtomicU64, 24 | max_cov: AtomicU64, 25 | } 26 | 27 | impl Stats { 28 | pub(crate) fn new() -> Self { 29 | Self::default() 30 | } 31 | 32 | pub(crate) fn inc_fuzzing(&self) { 33 | self.fuzzing.fetch_add(1, Ordering::Relaxed); 34 | } 35 | 36 | pub(crate) fn dec_fuzzing(&self) { 37 | self.fuzzing.fetch_sub(1, Ordering::Relaxed); 38 | } 39 | 40 | pub(crate) fn inc_repro(&self) { 41 | self.repro.fetch_add(1, Ordering::Relaxed); 42 | } 43 | 44 | pub(crate) fn dec_repro(&self) { 45 | self.repro.fetch_sub(1, Ordering::Relaxed); 46 | } 47 | 48 | pub(crate) fn set_re(&self, n: u64) { 49 | self.relations.store(n, Ordering::Relaxed); 50 | } 51 | 52 | pub(crate) fn set_unique_crash(&self, n: u64) { 53 | self.unique_crash.store(n, Ordering::Relaxed); 54 | } 55 | 56 | pub(crate) fn inc_crashes(&self) { 57 | self.crashes.fetch_add(1, Ordering::Relaxed); 58 | } 59 | 60 | pub(crate) fn inc_vm_restarts(&self) { 61 | self.vm_restarts.fetch_add(1, Ordering::Relaxed); 62 | } 63 | 64 | pub(crate) fn inc_corpus_size(&self) { 65 | self.corpus_size.fetch_add(1, Ordering::Relaxed); 66 | } 67 | 68 | pub(crate) fn inc_exec_total(&self) { 69 | self.exec_total.fetch_add(1, Ordering::Relaxed); 70 | } 71 | 72 | pub(crate) fn set_cal_cov(&self, n: u64) { 73 | self.cal_cov.store(n, Ordering::Relaxed); 74 | } 75 | 76 | pub(crate) fn set_max_cov(&self, n: u64) { 77 | self.max_cov.store(n, Ordering::Relaxed); 78 | } 79 | 80 | pub(crate) fn report>( 81 | &self, 82 | duration: Duration, 83 | out_dir: Option

, 84 | ) -> Result<()> { 85 | let mut f: Option = if let Some(d) = out_dir { 86 | let p = d.as_ref(); 87 | let f = OpenOptions::new() 88 | .create(true) 89 | .append(true) 90 | .write(true) 91 | .open(p) 92 | .context("report")?; 93 | Some(f) 94 | } else { 95 | None 96 | }; 97 | 98 | while !stop_soon() { 99 | sleep(duration); 100 | 101 | let fuzzing = self.fuzzing.load(Ordering::Relaxed); 102 | let repro = self.repro.load(Ordering::Relaxed); 103 | let crashes = self.crashes.load(Ordering::Relaxed); 104 | let unique_crash = self.unique_crash.load(Ordering::Relaxed); 105 | let corpus_size = self.corpus_size.load(Ordering::Relaxed); 106 | let exec_total = self.exec_total.load(Ordering::Relaxed); 107 | let corpus_cov = self.cal_cov.load(Ordering::Relaxed); 108 | let max_cov = self.max_cov.load(Ordering::Relaxed); 109 | log::info!( 110 | "exec: {}, fuzz/repro {}/{}, uniq/total crashes {}/{}, cal/max cover {}/{}, corpus: {}", 111 | exec_total, 112 | fuzzing, 113 | repro, 114 | unique_crash, 115 | crashes, 116 | corpus_cov, 117 | max_cov, 118 | corpus_size 119 | ); 120 | 121 | if let Some(f) = f.as_mut() { 122 | serde_json::to_writer_pretty(f, self).context("dump stats")?; 123 | } 124 | } 125 | 126 | Ok(()) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /syz_wrapper/src/exec/features.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use iota::iota; 3 | use thiserror::Error; 4 | 5 | pub type Features = u64; 6 | 7 | iota! { 8 | pub const FEATURE_COVERAGE: Features = 1 << (iota); 9 | ,FEATURE_COMPARISONS 10 | ,FEATURE_EXTRA_COVERAGE 11 | ,FEATURE_SANDBOX_SETUID 12 | ,FEATURE_SANDBOX_NAMESPACE 13 | ,FEATURE_SANDBOX_ANDROID 14 | ,FEATURE_FAULT 15 | ,FEATURE_LEAK 16 | ,FEATURE_NET_INJECTION 17 | ,FEATURE_NET_DEVICES 18 | ,FEATURE_KCSAN 19 | ,FEATURE_DEVLINK_PCI 20 | ,FEATURE_USB_EMULATION 21 | ,FEATURE_VHCI_INJECTION 22 | ,FEATURE_WIFI_EMULATION 23 | ,FEATURE_802154 24 | } 25 | 26 | pub const FEATURES_NAME: [&str; 16] = [ 27 | "code coverage", 28 | "comparison tracing", 29 | "extra coverage", 30 | "setuid sandbox", 31 | "namespace sandbox", 32 | "Android sandbox", 33 | "fault injection", 34 | "leak checking", 35 | "net packet injection", 36 | "net device setup", 37 | "concurrency sanitizer", 38 | "devlink PCI setup", 39 | "USB emulation", 40 | "hci packet injection", 41 | "wifi device emulation", 42 | "802.15.4 emulation", 43 | ]; 44 | 45 | #[derive(Debug, Error)] 46 | pub enum DetectFeaturesError { 47 | #[error("io: {0}")] 48 | Io(#[from] std::io::Error), 49 | #[error("detect: {0}")] 50 | Detect(String), 51 | } 52 | 53 | pub fn detect_features(mut cmd: Command) -> Result { 54 | cmd.arg("check"); 55 | let output = cmd.output()?; 56 | if output.status.success() { 57 | let out = output.stdout; 58 | assert_eq!(out.len(), 8); 59 | let mut val = [0; 8]; 60 | val.copy_from_slice(&out[0..]); 61 | Ok(u64::from_le_bytes(val)) 62 | } else { 63 | let err = String::from_utf8_lossy(&output.stderr).into_owned(); 64 | Err(DetectFeaturesError::Detect(format!( 65 | "'{:?}' : {}", 66 | cmd, err 67 | ))) 68 | } 69 | } 70 | 71 | #[derive(Debug, Error)] 72 | pub enum SetupFeaturesError { 73 | #[error("io: {0}")] 74 | Io(#[from] std::io::Error), 75 | #[error("setup: {0}")] 76 | Setup(String), 77 | } 78 | 79 | pub fn setup_features(mut cmd: Command, features: Features) -> Result<(), SetupFeaturesError> { 80 | let feature_args = features_to_args(features); 81 | if feature_args.is_empty() { 82 | return Ok(()); 83 | } 84 | 85 | cmd.arg("setup").args(&feature_args); 86 | let output = cmd.output()?; 87 | if !output.status.success() { 88 | let err = String::from_utf8_lossy(&output.stderr).into_owned(); 89 | return Err(SetupFeaturesError::Setup(format!( 90 | "failed to run '{:?}': {}", 91 | cmd, err 92 | ))); 93 | } 94 | 95 | Ok(()) 96 | } 97 | 98 | fn features_to_args(features: Features) -> Vec { 99 | let mut ret = Vec::new(); 100 | 101 | if features & FEATURE_LEAK != 0 { 102 | ret.push("leak".to_string()); 103 | } 104 | if features & FEATURE_FAULT != 0 { 105 | ret.push("fault".to_string()); 106 | } 107 | if features & FEATURE_KCSAN != 0 { 108 | ret.push("kcsan".to_string()); 109 | } 110 | if features & FEATURE_USB_EMULATION != 0 { 111 | ret.push("usb".to_string()); 112 | } 113 | if features & FEATURE_802154 != 0 { 114 | ret.push("802154".to_string()); 115 | } 116 | 117 | ret 118 | } 119 | 120 | pub fn features_to_env_flags(features: Features, env: &mut EnvFlags) { 121 | if features & FEATURE_EXTRA_COVERAGE != 0 { 122 | *env |= FLAG_EXTRA_COVER; 123 | } 124 | if features & FEATURE_NET_INJECTION != 0 { 125 | *env |= FLAG_ENABLE_TUN; 126 | } 127 | if features & FEATURE_NET_DEVICES != 0 { 128 | *env |= FLAG_ENABLE_NETDEV; 129 | } 130 | 131 | *env |= FLAG_ENABLE_NETRESET; 132 | *env |= FLAG_ENABLE_CGROUPS; 133 | *env |= FLAG_ENABLE_CLOSEFDS; 134 | 135 | if features & FEATURE_DEVLINK_PCI != 0 { 136 | *env |= FLAG_ENABLE_DEVLINKPCI; 137 | } 138 | if features & FEATURE_VHCI_INJECTION != 0 { 139 | *env |= FLAG_ENABLE_VHCI_INJECTION; 140 | } 141 | if features & FEATURE_WIFI_EMULATION != 0 { 142 | *env |= FLAG_ENABLE_WIFI; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /healer_core/src/corpus.rs: -------------------------------------------------------------------------------- 1 | use crate::{gen::choose_weighted, prog::Prog, HashMap, RngType}; 2 | use std::sync::RwLock; 3 | 4 | #[derive(Debug)] 5 | pub struct CorpusWrapper { 6 | pub inner: RwLock, 7 | } 8 | 9 | impl Default for CorpusWrapper { 10 | fn default() -> Self { 11 | Self { 12 | inner: RwLock::new(Corpus::default()), 13 | } 14 | } 15 | } 16 | 17 | impl CorpusWrapper { 18 | pub fn new() -> Self { 19 | Self { 20 | inner: RwLock::new(Corpus::new()), 21 | } 22 | } 23 | 24 | pub fn len(&self) -> usize { 25 | let inner = self.inner.read().unwrap(); 26 | inner.len() 27 | } 28 | 29 | pub fn is_empty(&self) -> bool { 30 | let inner = self.inner.read().unwrap(); 31 | inner.is_empty() 32 | } 33 | 34 | pub fn add_prog(&self, p: Prog, prio: u64) -> CorpusId { 35 | let mut inner = self.inner.write().unwrap(); 36 | inner.add_prog(p, prio) 37 | } 38 | 39 | pub fn select_one(&self, rng: &mut RngType) -> Option { 40 | let inner = self.inner.read().unwrap(); 41 | inner.select_one(rng).cloned() 42 | } 43 | 44 | pub fn culling(&self, f: F) -> usize 45 | where 46 | F: FnMut(&mut ProgInfo), 47 | { 48 | let mut inner = self.inner.write().unwrap(); 49 | inner.culling(f) 50 | } 51 | } 52 | 53 | pub type CorpusId = usize; 54 | 55 | #[derive(Debug, Clone, PartialEq, Eq)] 56 | pub struct ProgInfo { 57 | pub id: CorpusId, 58 | pub prog: Prog, 59 | pub prio: u64, 60 | } 61 | 62 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 63 | pub struct Corpus { 64 | progs: Vec, 65 | id_to_index: HashMap, 66 | next_id: CorpusId, 67 | prios: Vec, 68 | sum_prios: u64, 69 | } 70 | 71 | impl Corpus { 72 | #[inline] 73 | pub fn new() -> Self { 74 | Self::default() 75 | } 76 | 77 | #[inline] 78 | pub fn len(&self) -> usize { 79 | self.progs.len() 80 | } 81 | 82 | #[inline] 83 | pub fn is_empty(&self) -> bool { 84 | self.progs.is_empty() 85 | } 86 | 87 | fn next_id(&mut self) -> CorpusId { 88 | let ret = self.next_id; 89 | self.next_id += 1; 90 | ret 91 | } 92 | 93 | pub fn add_prog(&mut self, prog: Prog, prio: u64) -> CorpusId { 94 | debug_assert_ne!(prio, 0); 95 | let id = self.next_id(); 96 | self.add_prog_with_id(id, prog, prio); 97 | 98 | id 99 | } 100 | 101 | fn add_prog_with_id(&mut self, id: CorpusId, prog: Prog, prio: u64) { 102 | self.sum_prios += prio; 103 | self.prios.push(self.sum_prios); 104 | let p = ProgInfo { id, prog, prio }; 105 | let idx = self.progs.len(); 106 | self.progs.push(p); 107 | self.id_to_index.insert(id, idx); 108 | } 109 | 110 | pub fn get(&self, id: usize) -> Option<&Prog> { 111 | let idx = self.id_to_index.get(&id)?; 112 | self.progs.get(*idx).map(|p| &p.prog) 113 | } 114 | 115 | pub fn get_mut(&mut self, id: usize) -> Option<&mut Prog> { 116 | let idx = self.id_to_index.get(&id)?; 117 | self.progs.get_mut(*idx).map(|p| &mut p.prog) 118 | } 119 | 120 | pub fn select_one(&self, rng: &mut RngType) -> Option<&Prog> { 121 | if !self.is_empty() { 122 | let idx = choose_weighted(rng, &self.prios); 123 | Some(&self.progs[idx].prog) 124 | } else { 125 | None 126 | } 127 | } 128 | 129 | pub fn culling(&mut self, mut update: F) -> usize 130 | where 131 | F: FnMut(&mut ProgInfo), 132 | { 133 | let mut new_corpus = Corpus { 134 | progs: Vec::with_capacity(self.len()), 135 | id_to_index: HashMap::with_capacity(self.len()), 136 | next_id: self.next_id, // keep the old id count 137 | prios: Vec::with_capacity(self.len()), 138 | sum_prios: 0, 139 | }; 140 | 141 | let mut n = 0; 142 | let progs = std::mem::take(&mut self.progs); 143 | for mut p in progs { 144 | update(&mut p); 145 | if p.prio != 0 { 146 | n += 1; 147 | new_corpus.add_prog_with_id(p.id, p.prog, p.prio); 148 | } 149 | } 150 | *self = new_corpus; 151 | n 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /healer_core/src/ty/ptr.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ty::{common::CommonInfo, Dir}, 3 | value::{PtrValue, Value, VmaValue}, 4 | }; 5 | use std::{fmt::Display, ops::RangeInclusive}; 6 | 7 | use super::TypeId; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct VmaType { 11 | comm: CommonInfo, 12 | range: Option>, 13 | } 14 | 15 | impl VmaType { 16 | common_attr_getter! {} 17 | 18 | default_int_format_attr_getter! {} 19 | 20 | extra_attr_getter! {} 21 | 22 | #[inline(always)] 23 | pub fn format(&self) -> crate::ty::BinaryFormat { 24 | crate::ty::BinaryFormat::Native 25 | } 26 | 27 | #[inline(always)] 28 | pub fn range(&self) -> Option> { 29 | self.range.clone() 30 | } 31 | 32 | pub fn default_value(&self, dir: Dir) -> Value { 33 | VmaValue::new_special(self.id(), dir, 0).into() 34 | } 35 | 36 | pub fn is_default(&self, val: &Value) -> bool { 37 | let val = val.checked_as_vma(); 38 | val.is_special() && val.addr == 0 39 | } 40 | } 41 | 42 | impl Display for VmaType { 43 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 44 | write!(f, "vma")?; 45 | if let Some(range) = self.range.as_ref() { 46 | if range.start() == range.end() { 47 | write!(f, "[{}]", range.start())?; 48 | } else { 49 | write!(f, "[{}:{}]", range.start(), range.end())?; 50 | } 51 | } 52 | Ok(()) 53 | } 54 | } 55 | 56 | eq_ord_hash_impl!(VmaType); 57 | 58 | #[derive(Debug, Clone)] 59 | pub struct VmaTypeBuilder { 60 | comm: CommonInfo, 61 | range: Option>, 62 | } 63 | 64 | impl VmaTypeBuilder { 65 | pub fn new(comm: CommonInfo) -> Self { 66 | Self { comm, range: None } 67 | } 68 | 69 | pub fn comm(&mut self, comm: CommonInfo) -> &mut Self { 70 | self.comm = comm; 71 | self 72 | } 73 | 74 | pub fn range(&mut self, range: RangeInclusive) -> &mut Self { 75 | self.range = Some(range); 76 | self 77 | } 78 | 79 | pub fn build(self) -> VmaType { 80 | VmaType { 81 | comm: self.comm, 82 | range: self.range, 83 | } 84 | } 85 | } 86 | 87 | #[derive(Debug, Clone)] 88 | pub struct PtrType { 89 | comm: CommonInfo, 90 | elem: TypeId, // handle recursive type 91 | dir: Dir, 92 | } 93 | 94 | impl PtrType { 95 | pub const MAX_SPECIAL_POINTERS: u64 = 16; 96 | 97 | common_attr_getter! {} 98 | 99 | default_int_format_attr_getter! {} 100 | 101 | extra_attr_getter! {} 102 | 103 | #[inline(always)] 104 | pub fn format(&self) -> crate::ty::BinaryFormat { 105 | crate::ty::BinaryFormat::Native 106 | } 107 | 108 | #[inline(always)] 109 | pub fn elem(&self) -> TypeId { 110 | self.elem 111 | } 112 | 113 | #[inline(always)] 114 | pub fn dir(&self) -> Dir { 115 | self.dir 116 | } 117 | 118 | pub fn default_value(&self, dir: Dir) -> Value { 119 | // we don't have `target` here, so just give a null 120 | PtrValue::new_special(self.id(), dir, 0).into() 121 | } 122 | 123 | pub fn is_default(&self, val: &Value) -> bool { 124 | let val = val.checked_as_ptr(); 125 | val.is_special() 126 | } 127 | } 128 | 129 | impl Display for PtrType { 130 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 131 | write!(f, "ptr[{:?}, {}]", self.dir, self.elem) 132 | } 133 | } 134 | 135 | eq_ord_hash_impl!(PtrType); 136 | 137 | #[derive(Debug, Clone)] 138 | pub struct PtrTypeBuilder { 139 | comm: CommonInfo, 140 | elem: Option, 141 | dir: Dir, 142 | } 143 | 144 | impl PtrTypeBuilder { 145 | pub fn new(comm: CommonInfo) -> Self { 146 | Self { 147 | comm, 148 | elem: None, 149 | dir: Dir::In, 150 | } 151 | } 152 | 153 | pub fn comm(&mut self, comm: CommonInfo) -> &mut Self { 154 | self.comm = comm; 155 | self 156 | } 157 | 158 | pub fn elem(&mut self, elem: TypeId) -> &mut Self { 159 | self.elem = Some(elem); 160 | self 161 | } 162 | 163 | pub fn dir>(&mut self, dir: T) -> &mut Self { 164 | self.dir = dir.into(); 165 | self 166 | } 167 | 168 | pub fn build(self) -> PtrType { 169 | PtrType { 170 | comm: self.comm, 171 | elem: self.elem.unwrap(), 172 | dir: self.dir, 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /healer_core/src/mutation/int.rs: -------------------------------------------------------------------------------- 1 | //! Mutate value of `integer` like types. 2 | #[cfg(debug_assertions)] 3 | use crate::mutation::call::display_value_diff; 4 | use crate::{ 5 | context::Context, 6 | gen::int::{gen_flags_bitmask, gen_flags_non_bitmask, gen_int, gen_proc}, 7 | ty::IntType, 8 | value::Value, 9 | RngType, 10 | }; 11 | use rand::prelude::*; 12 | 13 | pub fn mutate_int(ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 14 | let ty = val.ty(ctx.target); 15 | 16 | if rng.gen() { 17 | let new_val = gen_int(ctx, rng, ty, val.dir()); 18 | debug_info!( 19 | "mutate_int(gen): {}", 20 | display_value_diff(val, &new_val, ctx.target) 21 | ); 22 | let mutated = new_val.checked_as_int().val != val.checked_as_int().val; 23 | *val = new_val; 24 | return mutated; 25 | } 26 | 27 | let val = val.checked_as_int_mut(); 28 | let ty = ty.checked_as_int(); 29 | let bit_sz = ty.bit_size(); 30 | let mut new_val = if ty.align() == 0 { 31 | do_mutate_int(rng, val.val, ty) 32 | } else { 33 | do_mutate_aligned_int(rng, val.val, ty) 34 | }; 35 | if bit_sz < 64 { 36 | new_val &= (1 << bit_sz) - 1; 37 | } 38 | 39 | debug_info!("mutate_int: {:#x} -> {:#x}", val.val, new_val); 40 | let mutated = val.val != new_val; 41 | val.val = new_val; 42 | 43 | mutated 44 | } 45 | 46 | fn do_mutate_int(rng: &mut RngType, old_val: u64, ty: &IntType) -> u64 { 47 | if rng.gen_ratio(1, 3) { 48 | old_val.wrapping_add(rng.gen_range(1..=4)) 49 | } else if rng.gen_ratio(1, 2) { 50 | old_val.wrapping_sub(rng.gen_range(1..=4)) 51 | } else { 52 | let bit_sz = ty.bit_size(); 53 | old_val ^ (1 << rng.gen_range(0..bit_sz)) 54 | } 55 | } 56 | 57 | fn do_mutate_aligned_int(rng: &mut RngType, old_val: u64, ty: &IntType) -> u64 { 58 | let r = ty.range().cloned().unwrap_or(0..=u64::MAX); 59 | let start = *r.start(); 60 | let mut end = *r.end(); 61 | if start == 0 && end == u64::MAX { 62 | end = 1_u64.wrapping_shl(ty.bit_size() as u32).wrapping_sub(1); 63 | } 64 | let index = old_val.wrapping_sub(start) / ty.align(); 65 | let miss = old_val.wrapping_sub(start) % ty.align(); 66 | let mut index = do_mutate_int(rng, index, ty); 67 | let last_index = end.wrapping_sub(start) / ty.align(); 68 | index %= last_index + 1; 69 | start 70 | .wrapping_add(index.wrapping_mul(ty.align())) 71 | .wrapping_add(miss) 72 | } 73 | 74 | pub fn mutate_const(_ctx: &mut Context, _rng: &mut RngType, _val: &mut Value) -> bool { 75 | debug_info!("mutate_const: doing nothing"); 76 | false 77 | } 78 | 79 | pub fn mutate_flags(ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 80 | let mut tries = 0; 81 | let mut mutated = false; 82 | let val = val.checked_as_int_mut(); 83 | let ty = val.ty(ctx.target).checked_as_flags(); 84 | 85 | let mut new_val = val.val; 86 | while tries < 128 && !mutated { 87 | new_val = if ty.bit_mask() { 88 | gen_flags_bitmask(rng, ty.vals(), val.val) 89 | } else { 90 | gen_flags_non_bitmask(rng, ty.vals(), val.val) 91 | }; 92 | mutated = new_val != val.val; 93 | tries += 1; 94 | } 95 | 96 | debug_info!("mutate_flags: {:#b} -> {:#b}", new_val, val.val); 97 | val.val = new_val; 98 | mutated 99 | } 100 | 101 | pub fn mutate_len(ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 102 | let ty = val.ty(ctx.target); 103 | let new_val = gen_int(ctx, rng, ty, val.dir()); 104 | debug_info!( 105 | "mutate_len: {}", 106 | display_value_diff(val, &new_val, ctx.target) 107 | ); 108 | *val = new_val; 109 | 110 | false // length mutation actually is meaning less 111 | } 112 | 113 | pub fn mutate_proc(ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 114 | let ty = val.ty(ctx.target); 115 | let new_val = gen_proc(ctx, rng, ty, val.dir()); 116 | debug_info!( 117 | "mutate_proc: {}", 118 | display_value_diff(val, &new_val, ctx.target) 119 | ); 120 | let mutated = new_val.checked_as_int().val != val.checked_as_int().val; 121 | *val = new_val; 122 | 123 | mutated 124 | } 125 | 126 | pub fn mutate_csum(ctx: &mut Context, rng: &mut RngType, val: &mut Value) -> bool { 127 | let ty = val.ty(ctx.target); 128 | let new_val = gen_int(ctx, rng, ty, val.dir()); 129 | debug_info!( 130 | "mutate_csum: {}", 131 | display_value_diff(val, &new_val, ctx.target) 132 | ); 133 | *val = new_val; 134 | 135 | false // csum mutation is meaning less 136 | } 137 | -------------------------------------------------------------------------------- /syz_wrapper/patches/features.h: -------------------------------------------------------------------------------- 1 | #ifndef FEATURES 2 | #define FEATURES 3 | 4 | #if GOOS_linux 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef __x86_64__ 17 | typedef unsigned long long u64; 18 | #else 19 | typedef uint64_t u64; 20 | #endif 21 | 22 | #define FEATURES_CHECK_SNIPPET \ 23 | do { \ 24 | if (argc == 2 && strcmp(argv[1], "check") == 0) { \ 25 | u64 features = to_le(check()); \ 26 | if (fwrite(&features, 1, 8, stdout) < 0) { \ 27 | fail("failed to write features to stdout"); \ 28 | } \ 29 | return 0; \ 30 | } \ 31 | } while (0) 32 | 33 | static inline void __swap(char* a, char* b) 34 | { 35 | char tmp = *a; 36 | *a = *b; 37 | *b = tmp; 38 | } 39 | 40 | static u64 to_le(u64 n) 41 | { 42 | int i = 1; 43 | if ((*(char*)(&i)) == 0) { // is bigendian? 44 | char* buf = (char*)&n; 45 | __swap(&buf[0], &buf[7]); 46 | __swap(&buf[1], &buf[6]); 47 | __swap(&buf[2], &buf[5]); 48 | __swap(&buf[3], &buf[4]); 49 | } 50 | return n; 51 | } 52 | 53 | static bool has_debugfs() 54 | { 55 | return access("/sys/kernel/debug", F_OK) == 0; 56 | } 57 | 58 | static bool has_kcov() 59 | { 60 | return has_debugfs() && access("/sys/kernel/debug/kcov", F_OK) == 0; 61 | } 62 | 63 | static bool has_fault() 64 | { 65 | return access("/proc/self/make-it-fail", F_OK) == 0 && 66 | access("/proc/thread-self/fail-nth", F_OK) == 0&& 67 | has_debugfs() && 68 | access("/sys/kernel/debug/failslab/ignore-gfp-wait", F_OK) == 0; 69 | } 70 | 71 | static bool has_leak() 72 | { 73 | if (!has_debugfs()) { 74 | return false; 75 | } 76 | int f; 77 | if ((f = open("/sys/kernel/debug/kmemleak", O_RDWR)) != 0) { 78 | return false; 79 | } 80 | if (write(f, "scan=off", 8) < 0) { 81 | return false; 82 | } 83 | close(f); 84 | return true; 85 | } 86 | 87 | static bool has_ns() 88 | { 89 | return access("/proc/self/ns/user", F_OK) == 0; 90 | } 91 | 92 | static bool has_android() 93 | { 94 | return access("/sys/fs/selinux/policy", F_OK) == 0; 95 | } 96 | 97 | static bool has_tun() 98 | { 99 | return access("/dev/net/tun", F_OK) == 0; 100 | } 101 | 102 | static bool has_usb() 103 | { 104 | return access("/dev/raw-gadget", F_OK) == 0; 105 | } 106 | 107 | static bool has_vhci() 108 | { 109 | return access("/dev/vhci", F_OK) == 0; 110 | } 111 | 112 | static bool has_kcsan() 113 | { 114 | return access("/sys/kernel/debug/kcsan", F_OK) == 0; 115 | } 116 | 117 | static bool has_devlink_pci() 118 | { 119 | return access("/sys/bus/pci/devices/0000:00:10.0/", F_OK) == 0; 120 | } 121 | 122 | static bool check_kversion(int major, int minor) 123 | { 124 | struct utsname buf; 125 | char* p; 126 | long ver[16]; 127 | int i = 0; 128 | 129 | if (uname(&buf) != 0) { 130 | return false; 131 | } 132 | p = buf.release; 133 | while (*p && i < 4) { 134 | if (isdigit(*p)) { 135 | ver[i] = strtol(p, &p, 10); 136 | i++; 137 | } else { 138 | p++; 139 | } 140 | } 141 | 142 | return i >= 2 && ver[1] * 1000 + ver[2] >= major * 1000 + minor; 143 | } 144 | 145 | static bool has_wifi() 146 | { 147 | return check_kversion(4, 17) && access("/sys/class/mac80211_hwsim/", F_OK) == 0; 148 | } 149 | 150 | static bool unused() 151 | { 152 | return false; 153 | } 154 | 155 | static bool enable() 156 | { 157 | return true; 158 | } 159 | 160 | static bool (*checkers[15])() = { 161 | has_kcov, // FEATURE_COVERAGE 162 | unused, // FEATURE_COMPARISONS 163 | unused, // FEATURE_EXTRA_COVERAGE 164 | enable, // FEATURE_SANDBOX_SETUID 165 | has_ns, // FEATURE_SANDBOX_NAMESPACE 166 | has_android, // FEATURE_SANDBOX_ANDROID 167 | has_fault, // FEATURE_FAULT 168 | has_leak, // FEATURE_LEAK 169 | has_tun, // FEATURE_NET_INJECTION 170 | enable, // FEATURE_NET_DEVICES 171 | has_kcsan, // FEATURE_KCSAN 172 | has_devlink_pci, // FEATURE_DEVLINK_PCI 173 | has_usb, // FEATURE_USB_EMULATION 174 | has_vhci, // FEATURE_VHCI_INJECTION 175 | has_wifi // FEATURE_WIFI_EMULATION 176 | }; 177 | 178 | static u64 check() 179 | { 180 | u64 ret = 0; 181 | int i; 182 | for (i = 0; i < 15; i++) { 183 | if (checkers[i]()) { 184 | ret |= (1 << i); 185 | } 186 | } 187 | 188 | return ret; 189 | } 190 | #else 191 | #error Currently, features has only supports linux. 192 | #endif 193 | 194 | #endif // FEATURES 195 | -------------------------------------------------------------------------------- /healer_core/src/mutation/seq.rs: -------------------------------------------------------------------------------- 1 | //! Sequence level mutation. 2 | use super::{foreach_call_arg_mut, restore_partial_ctx, restore_res_ctx}; 3 | use crate::{ 4 | context::Context, 5 | corpus::CorpusWrapper, 6 | gen::{gen_one_call, prog_len_range}, 7 | prog::{Call, Prog}, 8 | select::select_with_calls, 9 | syscall::SyscallId, 10 | value::ResValueKind, 11 | HashMap, 12 | RngType, 13 | lang_mod::mutate::select_call_to_wrapper, 14 | }; 15 | use rand::prelude::*; 16 | 17 | /// Select a prog from `corpus` and splice it with calls in the `ctx` randomly. 18 | pub fn splice(ctx: &mut Context, corpus: &CorpusWrapper, rng: &mut RngType) -> (bool, usize) { 19 | if ctx.calls.is_empty() || ctx.calls.len() > prog_len_range().end || corpus.is_empty() { 20 | return (false, 99); 21 | } 22 | 23 | let p = corpus.select_one(rng).unwrap(); 24 | let mut calls = p.calls; 25 | // mapping resource id of `calls`, continue with current `ctx.next_res_id` 26 | mapping_res_id(ctx, &mut calls); 27 | restore_partial_ctx(ctx, &calls); 28 | let idx = rng.gen_range(0..=ctx.calls.len()); 29 | debug_info!( 30 | "splice: splicing {} call(s) to location {}", 31 | calls.len(), 32 | idx 33 | ); 34 | ctx.calls.splice(idx..idx, calls); 35 | (true, 3) 36 | } 37 | 38 | /// Insert calls to random location of ctx's calls. 39 | pub fn insert_calls(ctx: &mut Context, _corpus: &CorpusWrapper, rng: &mut RngType) -> (bool, usize) { 40 | if ctx.calls.len() > prog_len_range().end { 41 | return (false, 99); 42 | } 43 | 44 | let idx = rng.gen_range(0..=ctx.calls.len()); 45 | restore_res_ctx(ctx, idx); // restore the resource information before call `idx` 46 | let (sid, op) = select_call_to_wrapper(ctx, rng, idx); 47 | debug_info!( 48 | "insert_calls: inserting {} to location {}", 49 | ctx.target.syscall_of(sid).name(), 50 | idx 51 | ); 52 | let mut calls_backup = std::mem::take(&mut ctx.calls); 53 | gen_one_call(ctx, rng, sid); 54 | let new_calls = std::mem::take(&mut ctx.calls); 55 | debug_info!("insert_calls: {} call(s) inserted", new_calls.len()); 56 | calls_backup.splice(idx..idx, new_calls); 57 | ctx.calls = calls_backup; 58 | (true, op) 59 | } 60 | 61 | pub fn remove_call(ctx: &mut Context, _corpus: &CorpusWrapper, rng: &mut RngType) -> (bool, usize) { 62 | if ctx.calls.is_empty() { 63 | return (false, 99); 64 | } 65 | 66 | let idx = rng.gen_range(0..ctx.calls.len()); 67 | let calls = std::mem::take(&mut ctx.calls); 68 | let mut p = Prog::new(calls); 69 | debug_info!("remove_call: removing call-{}", idx); 70 | p.remove_call_inplace(idx); 71 | ctx.calls = p.calls; 72 | (true, 4) 73 | } 74 | 75 | /// Select new call to location `idx`. 76 | pub fn select_call_to(ctx: &mut Context, rng: &mut RngType, idx: usize) -> SyscallId { 77 | let mut candidates: HashMap = HashMap::new(); 78 | let r = ctx.relation().inner.read().unwrap(); 79 | let calls = ctx.calls(); 80 | 81 | // first, consider calls that can be influenced by calls before `idx`. 82 | for sid in calls[..idx].iter().map(|c| c.sid()) { 83 | for candidate in r.influence_of(sid).iter().copied() { 84 | let entry = candidates.entry(candidate).or_default(); 85 | *entry += 1; 86 | } 87 | } 88 | 89 | // then, consider calls that can be influence calls after `idx`. 90 | if idx != calls.len() { 91 | for sid in calls[idx..].iter().map(|c| c.sid()) { 92 | for candidate in r.influence_by_of(sid).iter().copied() { 93 | let entry = candidates.entry(candidate).or_default(); 94 | *entry += 1; 95 | } 96 | } 97 | } 98 | 99 | let candidates: Vec<(SyscallId, u64)> = candidates.into_iter().collect(); 100 | if let Ok(candidate) = candidates.choose_weighted(rng, |candidate| candidate.1) { 101 | candidate.0 102 | } else { 103 | // failed to select with relation, use normal strategy. 104 | select_with_calls(ctx, rng) 105 | } 106 | } 107 | 108 | /// Mapping resource id of `calls`, make sure all `res_id` in `calls` is bigger then current `next_res_id` 109 | fn mapping_res_id(ctx: &mut Context, calls: &mut [Call]) { 110 | for call in calls { 111 | foreach_call_arg_mut(call, |val| { 112 | if let Some(val) = val.as_res_mut() { 113 | match &mut val.kind { 114 | ResValueKind::Ref(id) | ResValueKind::Own(id) => *id += ctx.next_res_id, 115 | ResValueKind::Null => (), 116 | } 117 | } 118 | }); 119 | for ids in call.generated_res.values_mut() { 120 | for id in ids { 121 | *id += ctx.next_res_id; 122 | } 123 | } 124 | for ids in call.used_res.values_mut() { 125 | for id in ids { 126 | *id += ctx.next_res_id; 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /healer_fuzzer/src/main.rs: -------------------------------------------------------------------------------- 1 | use env_logger::{Env, TimestampPrecision}; 2 | use healer_fuzzer::{boot, config::Config}; 3 | use healer_vm::qemu::QemuConfig; 4 | use std::path::PathBuf; 5 | use structopt::StructOpt; 6 | use syz_wrapper::{report::ReportConfig, repro::ReproConfig}; 7 | 8 | #[derive(Debug, StructOpt)] 9 | struct Settings { 10 | /// Target os to fuzz. 11 | #[structopt(long, short = "O", default_value = "linux")] 12 | os: String, 13 | /// Parallel fuzzing jobs. 14 | #[structopt(long, short = "j", default_value = "4")] 15 | job: u64, 16 | /// Directory to input prog. 17 | #[structopt(long, short = "i")] 18 | input: Option, 19 | /// Directory to write kinds of output data. 20 | #[structopt(long, short = "o", default_value = "output")] 21 | output: PathBuf, 22 | /// Path to kernel image. 23 | #[structopt(long, short = "k", default_value = "bzImage")] 24 | kernel_img: PathBuf, 25 | /// Path to disk image. 26 | #[structopt(long, short = "d", default_value = "stretch.img")] 27 | disk_img: PathBuf, 28 | /// Directory of target kernel object. 29 | #[structopt(long, short = "b")] 30 | kernel_obj_dir: Option, 31 | /// Source file directory of target kernel. 32 | #[structopt(long, short = "r")] 33 | kernel_src_dir: Option, 34 | /// Directory to syzkaller dir. 35 | #[structopt(long, short = "S", default_value = "./")] 36 | syz_dir: PathBuf, 37 | /// Relations file. 38 | #[structopt(long, short = "R")] 39 | relations: Option, 40 | /// Path to ssh secret key to login to os under test. 41 | #[structopt(long, short = "s", default_value = "./stretch.id_rsa")] 42 | ssh_key: PathBuf, 43 | /// Username to login os under test. 44 | #[structopt(long, short = "u", default_value = "root")] 45 | ssh_user: String, 46 | /// QEMU smp. 47 | #[structopt(long, short = "c", default_value = "2")] 48 | qemu_smp: u32, 49 | /// QEMU mem size in megabyte. 50 | #[structopt(long, short = "m", default_value = "4096")] 51 | qemu_mem: u32, 52 | /// Path to disabled syscalls. 53 | #[structopt(long)] 54 | disable_syscalls: Option, 55 | /// Path to crash white list. 56 | #[structopt(long)] 57 | crash_whitelist: Option, 58 | /// Number of instance used for repro. 59 | #[structopt(long, default_value = "2")] 60 | repro_vm_count: u64, 61 | /// Disable call fault injection. 62 | #[structopt(long)] 63 | disable_fault_injection: bool, 64 | /// Disable crash repro. 65 | #[structopt(long)] 66 | disable_repro: bool, 67 | /// Whitelist for fault injection. 68 | #[structopt(long)] 69 | fault_injection_whitelist: Option, 70 | /// Dump stats in json format to output dir. 71 | #[structopt(long)] 72 | bench: bool, 73 | /// Debug mode. 74 | #[structopt(long)] 75 | debug: bool, 76 | } 77 | 78 | fn main() -> anyhow::Result<()> { 79 | let settings = Settings::from_args(); 80 | 81 | let log_env = Env::new() 82 | .filter_or("HEALER_LOG", "info") 83 | .default_write_style_or("auto"); 84 | env_logger::Builder::from_env(log_env) 85 | .format_module_path(false) 86 | .format_target(false) 87 | .format_timestamp(Some(TimestampPrecision::Seconds)) 88 | .init(); 89 | 90 | let config = Config { 91 | os: settings.os, 92 | relations: settings.relations, 93 | input: settings.input, 94 | crash_whitelist: settings.crash_whitelist, 95 | job: settings.job, 96 | syz_dir: settings.syz_dir, 97 | output: settings.output, 98 | disabled_calls: settings.disable_syscalls, 99 | disable_fault_injection: settings.disable_fault_injection, 100 | fault_injection_whitelist_path: settings.fault_injection_whitelist, 101 | disable_repro: settings.disable_repro, 102 | qemu_config: QemuConfig { 103 | qemu_smp: settings.qemu_smp, 104 | qemu_mem: settings.qemu_mem, 105 | ssh_key: settings.ssh_key.to_str().unwrap().to_string(), 106 | ssh_user: settings.ssh_user, 107 | kernel_img: Some(settings.kernel_img.to_str().unwrap().to_string()), 108 | disk_img: settings.disk_img.to_str().unwrap().to_string(), 109 | ..Default::default() 110 | }, 111 | repro_config: ReproConfig { 112 | qemu_count: settings.repro_vm_count, 113 | ..Default::default() 114 | }, 115 | report_config: ReportConfig { 116 | kernel_obj_dir: settings 117 | .kernel_obj_dir 118 | .map(|s| s.to_str().unwrap().to_string()), 119 | kernel_src_dir: settings 120 | .kernel_src_dir 121 | .map(|s| s.to_str().unwrap().to_string()), 122 | ..Default::default() 123 | }, 124 | bench: settings.bench, 125 | debug: settings.debug, 126 | ..Default::default() 127 | }; 128 | 129 | boot(config) 130 | } 131 | -------------------------------------------------------------------------------- /healer_core/src/gen/res.rs: -------------------------------------------------------------------------------- 1 | //! Generate value for `resource` type. 2 | use crate::{ 3 | context::Context, 4 | gen::{current_builder, gen_one_call}, 5 | target::Target, 6 | ty::{Dir, ResKind, ResType, Type}, 7 | value::{ResValue, ResValueId, Value}, 8 | RngType, 9 | }; 10 | use rand::{prelude::SliceRandom, Rng}; 11 | use std::cell::Cell; 12 | 13 | type ResGenerator = fn(&mut Context, &mut RngType, &ResType, Dir) -> Option; 14 | const RES_GENERATORS: [ResGenerator; 2] = [res_reusing, generate_res_output_call]; 15 | 16 | pub fn gen_res(ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 17 | let ty = ty.checked_as_res(); 18 | 19 | if dir == Dir::Out || dir == Dir::InOut && rng.gen() { 20 | return generate_res(ctx, ty); 21 | } 22 | 23 | for g in RES_GENERATORS { 24 | if rng.gen_ratio(1, 10) { 25 | continue; 26 | } 27 | if let Some(val) = g(ctx, rng, ty, dir) { 28 | return val; 29 | } 30 | } 31 | 32 | let val = ty.special_vals().choose(rng).copied().unwrap_or(0); 33 | ResValue::new_null(ty.id(), dir, val).into() 34 | } 35 | 36 | #[inline] 37 | fn generate_res(ctx: &mut Context, ty: &ResType) -> Value { 38 | let id = ctx.next_res_id(); 39 | current_builder(|c| { 40 | c.record_res(ty.res_name(), id); 41 | }); 42 | ctx.record_res(ty.res_name(), id); 43 | ResValue::new_res(ty.id(), id, ty.special_vals()[0]).into() 44 | } 45 | 46 | #[inline] 47 | fn record_used_res(kind: &ResKind, id: ResValueId) { 48 | current_builder(|c| { 49 | c.used_res(kind, id); 50 | }); 51 | } 52 | 53 | thread_local! { 54 | static GENERATING_RES: Cell = Cell::new(false); 55 | } 56 | 57 | #[inline] 58 | fn mark_generating() { 59 | GENERATING_RES.with(|g| g.set(true)) 60 | } 61 | 62 | #[inline] 63 | fn generating() -> bool { 64 | GENERATING_RES.with(|g| g.get()) 65 | } 66 | 67 | #[inline] 68 | fn generate_done() { 69 | GENERATING_RES.with(|g| g.set(false)) 70 | } 71 | 72 | fn generate_res_output_call( 73 | ctx: &mut Context, 74 | rng: &mut RngType, 75 | ty: &ResType, 76 | dir: Dir, 77 | ) -> Option { 78 | if generating() { 79 | return None; 80 | } 81 | 82 | mark_generating(); 83 | debug_info!("generating resource: {}", ty.res_name()); 84 | let mut ret = None; 85 | let target = ctx.target(); 86 | let mut tries = 0; 87 | while tries != 3 { 88 | let kind = res_mapping(target, rng, ty.res_name()); 89 | let candidates = target.res_output_syscall(kind); 90 | if candidates.is_empty() { 91 | tries += 1; 92 | continue; 93 | } 94 | let sid = candidates.choose(rng).unwrap(); 95 | gen_one_call(ctx, rng, *sid); 96 | let new_res = &ctx.calls().last().unwrap().generated_res; 97 | if let Some(ids) = new_res.get(kind) { 98 | if let Some(id) = ids.choose(rng) { 99 | record_used_res(kind, *id); 100 | ret = Some(ResValue::new_ref(ty.id(), dir, *id).into()); 101 | break; 102 | } 103 | } 104 | tries += 1; 105 | } 106 | generate_done(); 107 | 108 | ret 109 | } 110 | 111 | /// Mapping resource to its super/sub type randomly 112 | fn res_mapping<'a>(target: &'a Target, rng: &mut RngType, kind: &'a ResKind) -> &'a ResKind { 113 | if target.res_output_syscall(kind).is_empty() || rng.gen_ratio(1, 10) { 114 | do_res_mapping(target, rng, kind) 115 | } else { 116 | kind 117 | } 118 | } 119 | 120 | fn do_res_mapping<'a>(target: &'a Target, rng: &mut RngType, kind: &'a ResKind) -> &'a ResKind { 121 | let kinds = if !target.res_sub_tys(kind).is_empty() && rng.gen_ratio(4, 5) { 122 | target.res_sub_tys(kind) 123 | } else { 124 | target.res_super_tys(kind) 125 | }; 126 | 127 | let mut tries = 0; 128 | let max = std::cmp::min(kinds.len(), 16); 129 | let mut ret = kind; 130 | while tries != max { 131 | let kind = kinds.choose(rng).unwrap(); 132 | if !target.res_output_syscall(kind).is_empty() { 133 | ret = kind; 134 | break; 135 | } 136 | tries += 1; 137 | } 138 | ret 139 | } 140 | 141 | fn res_reusing(ctx: &mut Context, rng: &mut RngType, ty: &ResType, dir: Dir) -> Option { 142 | let kinds = ctx 143 | .res() 144 | .iter() 145 | .filter(|r| should_use(ctx.target(), rng, ty, r)) 146 | .collect::>(); 147 | let kind = kinds.choose(rng).copied()?; 148 | let id = ctx.res_ids()[kind].choose(rng).copied()?; 149 | record_used_res(kind, id); 150 | Some(ResValue::new_ref(ty.id(), dir, id).into()) 151 | } 152 | 153 | fn should_use(target: &Target, rng: &mut RngType, dst: &ResType, src_kind: &ResKind) -> bool { 154 | let dst_kind = dst.res_name(); 155 | let is_sub_ty = target.res_sub_tys(dst_kind).binary_search(src_kind).is_ok(); 156 | if is_sub_ty { 157 | return true; 158 | } 159 | let dst_kind = &dst.kinds()[0]; 160 | let use_similar = 161 | rng.gen_ratio(1, 50) && target.res_sub_tys(dst_kind).binary_search(src_kind).is_ok(); 162 | use_similar 163 | } 164 | -------------------------------------------------------------------------------- /tools/model_manager/api/lang_model/model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import copy 4 | from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence 5 | from .utils import AvgrageMeter, accuracy, adjust_learning_rate 6 | 7 | class RelationModel(nn.Module): 8 | def __init__(self, hidden_dim, embed_dim, vocab_size, 9 | device, num_layers=2, dropout=0.5): 10 | super(RelationModel, self).__init__() 11 | self.hidden_dim = hidden_dim 12 | self.embed_dim = embed_dim 13 | self.num_layers = num_layers 14 | self.embedding = nn.Embedding(vocab_size, embed_dim) 15 | self.lstm = nn.LSTM(embed_dim, hidden_dim, bidirectional=True, 16 | dropout=dropout, num_layers=num_layers) 17 | self.decoder = nn.Linear(2*hidden_dim, vocab_size) 18 | self.device=device 19 | 20 | def forward(self, inputs, lengths, hidden=None): 21 | self.lstm.flatten_parameters() 22 | batch_size, seq_len = inputs.size() 23 | embeds = self.embedding(inputs) 24 | embeds = pack_padded_sequence(embeds, lengths, batch_first=True) 25 | 26 | if not hidden: 27 | h0 = torch.zeros(2*self.num_layers, batch_size, self.hidden_dim, device=self.device).to(self.device) 28 | c0 = torch.zeros(2*self.num_layers, batch_size, self.hidden_dim, device=self.device).to(self.device) 29 | hidden = (h0, c0) 30 | 31 | states, hidden = self.lstm(embeds, hidden) 32 | states, _ = pad_packed_sequence(states, total_length=seq_len, batch_first=True) 33 | 34 | outputs = self.decoder(states).reshape(batch_size*seq_len, -1) 35 | return outputs 36 | 37 | 38 | def train(train_loader, test_loader, model, 39 | model_path, num_epoch, optimizer, 40 | lr, criterion, device): 41 | top1 = AvgrageMeter() 42 | top5 = AvgrageMeter() 43 | top10 = AvgrageMeter() 44 | 45 | best_topN = -1 46 | best_model = None 47 | shape = None 48 | 49 | for epoch in range(num_epoch): 50 | model.train() 51 | adjust_learning_rate(optimizer, epoch, lr) 52 | train_loss = 0.0 53 | for i, (inputs, labels, lengths) in enumerate(train_loader): 54 | inputs = inputs.to(device) 55 | labels = labels.to(device) 56 | 57 | lengths, sorted_indices = torch.sort(lengths, descending=True) 58 | sorted_indices = sorted_indices.to(device) 59 | inputs = inputs.index_select(0, sorted_indices).to(device) 60 | labels = labels.index_select(0, sorted_indices).reshape(-1,).to(device) 61 | 62 | outputs = model(inputs, lengths) 63 | 64 | loss = criterion(outputs,labels) 65 | optimizer.zero_grad() 66 | loss.backward() 67 | optimizer.step() 68 | 69 | prec1, prec5, prec10 = accuracy(outputs, labels, lengths, (1,5,10), device) 70 | n = inputs.size(0) 71 | top1.update(prec1.item(), n) 72 | top5.update(prec5.item(), n) 73 | top10.update(prec10.item(), n) 74 | train_loss += loss.item() 75 | 76 | if epoch % 10 == 0: 77 | print(f"\nepoch {epoch} train, train loss: {train_loss/(i+1)}, top1_acc: {top1.avg}, top5_acc: {top5.avg}, top10_acc: {top10.avg}") 78 | eval_top1, eval_top5, eval_top10 = eval(test_loader, model, device) 79 | print(f"\nepoch {epoch} eval, top1_acc: {eval_top1}, top5_acc: {eval_top5}, top10_acc: {eval_top10}") 80 | traced_script_module = torch.jit.trace(model, (inputs, lengths)) 81 | traced_script_module.save(f"{model_path}/syscall_model_jit_epoch_{epoch}.pt") 82 | torch.save(model.state_dict(), f'{model_path}/syscall_model_epoch_{epoch}.model') 83 | if eval_top10 > best_topN: 84 | best_topN = eval_top10 85 | best_model = copy.deepcopy(model) 86 | shape = (inputs, lengths) 87 | 88 | traced_script_module = torch.jit.trace(best_model, shape) 89 | traced_script_module.save(f"{model_path}/syscall_model_jit_best.pt") 90 | torch.save(model.state_dict(), f'{model_path}/syscall_model_best.model') 91 | 92 | return f"{model_path}/syscall_model_jit_best.pt" if best_model else None 93 | 94 | 95 | def eval(data_loader, model, device): 96 | model.eval() 97 | top1 = AvgrageMeter() 98 | top5 = AvgrageMeter() 99 | top10 = AvgrageMeter() 100 | with torch.no_grad(): 101 | for i, (inputs, labels, lengths) in enumerate(data_loader): 102 | inputs = inputs.to(device) 103 | labels = labels.to(device) 104 | 105 | lengths, sorted_indices = torch.sort(lengths, descending=True) 106 | sorted_indices = sorted_indices.to(device) 107 | inputs = inputs.index_select(0, sorted_indices).to(device) 108 | labels = labels.index_select(0, sorted_indices).reshape(-1,).to(device) 109 | 110 | outputs = model(inputs, lengths).to(device) 111 | prec1, prec5, prec10 = accuracy(outputs, labels, lengths, (1,5,10), device) 112 | 113 | n = inputs.size(0) 114 | top1.update(prec1.item(), n) 115 | top5.update(prec5.item(), n) 116 | top10.update(prec10.item(), n) 117 | 118 | return top1.avg, top5.avg, top10.avg 119 | -------------------------------------------------------------------------------- /healer_core/src/syscall.rs: -------------------------------------------------------------------------------- 1 | //! AST of Syzlang description 2 | 3 | use crate::ty::{Field, Type}; 4 | use std::fmt::Display; 5 | 6 | pub type SyscallId = usize; 7 | pub const MAX_PARAMS_NUM: usize = 9; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct Syscall { 11 | /// Unique id of each declared syscall. 12 | id: SyscallId, 13 | /// Kernel call number. 14 | nr: u64, 15 | /// Name in syslang description. 16 | name: Box, 17 | /// Syscall name. 18 | call_name: Box, 19 | /// Syz: number of trailing args that should be zero-filled 20 | missing_args: u64, 21 | params: Box<[Field]>, 22 | ret: Option, 23 | attr: SyscallAttr, 24 | } 25 | 26 | impl Display for Syscall { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | write!(f, "{}(", self.name)?; 29 | for (i, param) in self.params.iter().enumerate() { 30 | write!(f, "{}", param)?; 31 | if i != self.params.len() - 1 { 32 | write!(f, ", ")?; 33 | } 34 | } 35 | write!(f, ")")?; 36 | if let Some(r) = self.ret.as_ref() { 37 | write!(f, " {}", r.name())?; 38 | } 39 | if !self.attr.is_default() { 40 | write!(f, " ({})", self.attr)?; 41 | } 42 | Ok(()) 43 | } 44 | } 45 | 46 | impl Syscall { 47 | #[inline(always)] 48 | pub fn id(&self) -> SyscallId { 49 | self.id 50 | } 51 | 52 | #[inline(always)] 53 | pub fn nr(&self) -> u64 { 54 | self.nr 55 | } 56 | 57 | #[inline(always)] 58 | pub fn name(&self) -> &str { 59 | &self.name 60 | } 61 | 62 | #[inline(always)] 63 | pub fn call_name(&self) -> &str { 64 | &self.call_name 65 | } 66 | 67 | #[inline(always)] 68 | pub fn missing_args(&self) -> u64 { 69 | self.missing_args 70 | } 71 | 72 | #[inline(always)] 73 | pub fn params(&self) -> &[Field] { 74 | &self.params 75 | } 76 | 77 | #[inline(always)] 78 | pub fn ret(&self) -> Option<&Type> { 79 | self.ret.as_ref() 80 | } 81 | 82 | #[inline(always)] 83 | pub fn attr(&self) -> &SyscallAttr { 84 | &self.attr 85 | } 86 | } 87 | 88 | #[derive(Debug, Clone, Default)] 89 | pub struct SyscallBuilder { 90 | id: Option, 91 | nr: Option, 92 | name: Option, 93 | call_name: Option, 94 | missing_args: Option, 95 | params: Vec, 96 | ret: Option, 97 | attr: Option, 98 | } 99 | 100 | impl SyscallBuilder { 101 | pub fn new() -> Self { 102 | Self::default() 103 | } 104 | 105 | pub fn id(&mut self, id: SyscallId) -> &mut Self { 106 | self.id = Some(id); 107 | self 108 | } 109 | 110 | pub fn nr(&mut self, nr: u64) -> &mut Self { 111 | self.nr = Some(nr); 112 | self 113 | } 114 | 115 | pub fn name>(&mut self, name: T) -> &mut Self { 116 | self.name = Some(name.into()); 117 | self 118 | } 119 | 120 | pub fn call_name>(&mut self, call_name: T) -> &mut Self { 121 | self.call_name = Some(call_name.into()); 122 | self 123 | } 124 | 125 | pub fn missing_args(&mut self, missing_args: u64) -> &mut Self { 126 | self.missing_args = Some(missing_args); 127 | self 128 | } 129 | 130 | pub fn params(&mut self, params: Vec) -> &mut Self { 131 | self.params = params; 132 | self 133 | } 134 | 135 | pub fn ret(&mut self, ret: Type) -> &mut Self { 136 | self.ret = Some(ret); 137 | self 138 | } 139 | 140 | pub fn attr(&mut self, attr: SyscallAttr) -> &mut Self { 141 | self.attr = Some(attr); 142 | self 143 | } 144 | 145 | pub fn build(self) -> Syscall { 146 | Syscall { 147 | id: self.id.unwrap(), 148 | nr: self.nr.unwrap_or_default(), 149 | name: self.name.clone().unwrap().into_boxed_str(), 150 | call_name: if let Some(call_name) = self.call_name { 151 | call_name.into_boxed_str() 152 | } else { 153 | self.name.unwrap().into_boxed_str() 154 | }, 155 | missing_args: self.missing_args.unwrap_or_default(), 156 | params: self.params.into_boxed_slice(), 157 | ret: self.ret, 158 | attr: self.attr.unwrap_or_default(), 159 | } 160 | } 161 | } 162 | 163 | #[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd)] 164 | pub struct SyscallAttr { 165 | pub disabled: bool, 166 | pub timeout: u64, 167 | pub prog_timeout: u64, 168 | pub ignore_return: bool, 169 | pub breaks_returns: bool, 170 | } 171 | 172 | impl SyscallAttr { 173 | pub fn is_default(&self) -> bool { 174 | *self == Self::default() 175 | } 176 | } 177 | 178 | impl Display for SyscallAttr { 179 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 180 | use std::fmt::Write; 181 | let mut out = String::new(); 182 | if self.disabled { 183 | write!(out, "disabled, ")?; 184 | } 185 | if self.timeout != 0 { 186 | write!(out, "timeout[{}], ", self.timeout)?; 187 | } 188 | if self.prog_timeout != 0 { 189 | write!(out, "prog_timeout[{}], ", self.prog_timeout)?; 190 | } 191 | if self.ignore_return { 192 | write!(out, "ignore_return, ")?; 193 | } 194 | if self.breaks_returns { 195 | write!(out, "breaks_returns, ")?; 196 | } 197 | write!(f, "{}", out.trim().trim_matches(',')) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /healer_core/src/select.rs: -------------------------------------------------------------------------------- 1 | //! Call Selection. 2 | //! 3 | //! Select syscalls based on syscalls, syscall => input/output resources, resources => input/output syscalls, 4 | //! resource => sub/super types, relations. 5 | use crate::{context::Context, gen::choose_weighted, syscall::SyscallId, HashMap, RngType, lang_mod::mutate::top_k}; 6 | use rand::prelude::*; 7 | 8 | /// Select a syscall based on current calls context. 9 | #[inline] 10 | pub fn select(ctx: &Context, rng: &mut RngType) -> SyscallId { 11 | if ctx.calls().is_empty() { 12 | select_with_no_calls(ctx, rng) 13 | } else { 14 | select_with_calls(ctx, rng) 15 | } 16 | } 17 | 18 | /// Weight for selector. 19 | type Weight = u64; 20 | type Selector = fn(&Context, &mut RngType) -> Option; 21 | 22 | /// Select a syscall without any initial call 23 | pub fn select_with_no_calls(ctx: &Context, rng: &mut RngType) -> SyscallId { 24 | /// Selectors that can be used when initial calls are given. 25 | const SELECTORS: [Selector; 3] = [ 26 | select_res_output_syscall, // 80% 27 | select_calls_wrapper, // 10% 28 | select_random_syscall, // 10% 29 | ]; 30 | const WEIGHTS: [Weight; 3] = [80, 90, 100]; 31 | 32 | loop { 33 | let idx = choose_weighted(rng, &WEIGHTS); 34 | if let Some(sid) = SELECTORS[idx](ctx, rng) { 35 | debug_info!("select with no calls, strategy-{}: {}", idx, ctx.target().syscall_of(sid)); 36 | return sid; 37 | } 38 | } 39 | 40 | // loop { 41 | // let selector = if rng.gen_ratio(9, 10) { 42 | // select_res_output_syscall 43 | // } else { 44 | // select_random_syscall 45 | // }; 46 | // if let Some(sid) = selector(ctx, rng) { 47 | // debug_info!("select with no calls: {}", ctx.target().syscall_of(sid)); 48 | // return sid; 49 | // } 50 | // } 51 | } 52 | 53 | /// Select based on generated calls. 54 | pub fn select_with_calls(ctx: &Context, rng: &mut RngType) -> SyscallId { 55 | /// Selectors that can be used when initial calls are given. 56 | const SELECTORS: [Selector; 4] = [ 57 | select_calls_wrapper, // 50% 58 | select_res_input_syscall, // 20% 59 | select_res_output_syscall, // 15% 60 | select_random_syscall, // 10% 61 | ]; 62 | const WEIGHTS: [Weight; 4] = [60, 80, 95, 100]; 63 | 64 | loop { 65 | let idx = choose_weighted(rng, &WEIGHTS); 66 | if let Some(sid) = SELECTORS[idx](ctx, rng) { 67 | debug_info!("select strategy-{}: {}", idx, ctx.target().syscall_of(sid)); 68 | return sid; 69 | } 70 | } 71 | } 72 | 73 | #[inline] 74 | pub fn select_calls_wrapper(ctx: &Context, rng: &mut RngType) -> Option { 75 | if ctx.model().exists() { 76 | select_with_model(ctx, rng) 77 | } 78 | else { 79 | select_with_relation(ctx, rng) 80 | } 81 | } 82 | 83 | /// Select based on generated calls and relations. 84 | pub fn select_with_relation(ctx: &Context, rng: &mut RngType) -> Option { 85 | let mut candidates: HashMap = HashMap::new(); 86 | let r = ctx.relation().inner.read().unwrap(); 87 | let calls = ctx.calls(); 88 | 89 | for sid in calls.iter().map(|c| c.sid()) { 90 | for candidate in r.influence_of(sid).iter().copied() { 91 | let entry = candidates.entry(candidate).or_default(); 92 | *entry += 1; 93 | } 94 | } 95 | 96 | let candidates: Vec<(SyscallId, Weight)> = candidates.into_iter().collect(); 97 | candidates 98 | .choose_weighted(rng, |candidate| candidate.1) 99 | .ok() 100 | .map(|candidate| candidate.0) 101 | } 102 | 103 | /// Select based on generated calls and model. 104 | pub fn select_with_model(ctx: &Context, rng: &mut RngType) -> Option { 105 | let model = ctx.model().inner.read().unwrap(); 106 | let calls = ctx.calls(); 107 | 108 | let topk = 10; 109 | let mut prev_calls: Vec = calls.iter().map(|c| c.sid()+3).collect(); 110 | // "2" refer to Start-Of-Sentence(SOS) 111 | prev_calls.insert(0, 2); 112 | let prev_pred = model.eval(&prev_calls).unwrap(); 113 | let candidates = top_k(&prev_pred, topk); 114 | let candidates: Vec<(SyscallId, f64)> = candidates.into_iter().collect::>(); 115 | if let Some(sid) = candidates 116 | .choose_weighted(rng, |candidate| candidate.1) 117 | .ok() 118 | .map(|candidate| candidate.0) { 119 | if sid >= 3 { 120 | return Some(sid-3) 121 | } 122 | else { 123 | return None 124 | } 125 | } 126 | None 127 | } 128 | 129 | /// Select syscall that can output resources. 130 | pub fn select_res_output_syscall(ctx: &Context, rng: &mut RngType) -> Option { 131 | let selected_res_kind = if ctx.res().is_empty() || rng.gen_ratio(1, 5) { 132 | ctx.target().res_kinds().choose(rng).unwrap() 133 | } else { 134 | let res_base = ctx.res().choose(rng).unwrap(); 135 | if rng.gen() { 136 | ctx.target().res_sub_tys(res_base).choose(rng).unwrap() 137 | } else { 138 | ctx.target().res_super_tys(res_base).choose(rng).unwrap() 139 | } 140 | }; 141 | ctx.target() 142 | .res_output_syscall(selected_res_kind) 143 | .choose(rng) 144 | .copied() 145 | } 146 | 147 | /// Select syscall that consume current resource 148 | pub fn select_res_input_syscall(ctx: &Context, rng: &mut RngType) -> Option { 149 | if let Some(mut res) = ctx.res().choose(rng) { 150 | if rng.gen_ratio(3, 10) { 151 | // use syscalls that take super type of 'res' as input 152 | res = ctx.target().res_sub_tys(res).choose(rng).unwrap(); 153 | } 154 | ctx.target().res_input_syscall(res).choose(rng).copied() 155 | } else { 156 | None 157 | } 158 | } 159 | 160 | /// Select syscall randomly 161 | pub fn select_random_syscall(ctx: &Context, rng: &mut RngType) -> Option { 162 | ctx.target().enabled_syscalls().choose(rng).map(|s| s.id()) 163 | } 164 | -------------------------------------------------------------------------------- /syz_wrapper/src/repro.rs: -------------------------------------------------------------------------------- 1 | use crate::exec::{ExecOpt, FLAG_INJECT_FAULT}; 2 | use healer_core::{prog::Prog, target::Target}; 3 | use simd_json::{json, to_string}; 4 | use std::env::temp_dir; 5 | use std::fmt::Write; 6 | use std::fs::write; 7 | use std::path::PathBuf; 8 | use std::process::Command; 9 | use thiserror::Error; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct ReproInfo { 13 | pub log: String, 14 | pub opt: String, 15 | pub p: String, 16 | pub c_prog: Option, 17 | pub repro_log: String, 18 | } 19 | 20 | #[derive(Debug, Error)] 21 | pub enum ReproError { 22 | #[error("io: {0}")] 23 | Io(#[from] std::io::Error), 24 | } 25 | 26 | #[derive(Debug, Clone)] 27 | pub struct ReproConfig { 28 | pub id: u64, 29 | pub target: String, 30 | pub syz_dir: String, 31 | pub work_dir: String, 32 | pub disk_img: String, 33 | pub kernel_img: String, 34 | pub ssh_key: String, 35 | pub qemu_count: u64, 36 | pub qemu_smp: usize, 37 | pub qemu_mem: usize, 38 | } 39 | 40 | impl Default for ReproConfig { 41 | fn default() -> Self { 42 | Self { 43 | id: 0, 44 | target: "linux/amd64".to_string(), 45 | syz_dir: "./".to_string(), 46 | work_dir: "./".to_string(), 47 | kernel_img: "./bzImage".to_string(), 48 | disk_img: "./stretch.img".to_string(), 49 | ssh_key: "./stretch.id_rsa".to_string(), 50 | qemu_count: 1, 51 | qemu_smp: 2, 52 | qemu_mem: 4096, 53 | } 54 | } 55 | } 56 | 57 | impl ReproConfig { 58 | pub fn check(&mut self) -> Result<(), String> { 59 | let syz_dir = PathBuf::from(&self.syz_dir); 60 | let syz_repro = syz_dir.join("bin").join("syz-repro"); 61 | if !syz_repro.exists() { 62 | Err(format!("{} not exists", syz_repro.display())) 63 | } else { 64 | Ok(()) 65 | } 66 | } 67 | } 68 | 69 | pub fn repro( 70 | config: &ReproConfig, 71 | target: &Target, 72 | crash_log: &[u8], 73 | run_history: &[(ExecOpt, Prog)], 74 | ) -> Result, ReproError> { 75 | let log = build_log(target, run_history, crash_log); 76 | let tmp_log = temp_dir().join(format!("healer-run_log-{}.tmp", config.id)); 77 | write(&tmp_log, &log)?; 78 | let syz_conf = syz_conf(config); 79 | let tmp_conf = temp_dir().join(format!("healer-syz_conf-{}.tmp", config.id)); 80 | write(&tmp_conf, syz_conf.as_bytes())?; 81 | let syz_dir = PathBuf::from(&config.syz_dir); 82 | let syz_repro = Command::new(syz_dir.join("bin").join("syz-repro")) 83 | .arg("-config") 84 | .arg(&tmp_conf) 85 | .arg(&tmp_log) 86 | .output()?; 87 | if syz_repro.status.success() { 88 | let log = String::from_utf8_lossy(&log).into_owned(); 89 | let repro_log = String::from_utf8_lossy(&syz_repro.stdout).into_owned(); 90 | Ok(parse_repro_log(log, repro_log)) 91 | } else { 92 | Ok(None) // TODO return run_history, if repro failed 93 | } 94 | } 95 | 96 | #[allow(clippy::while_let_on_iterator)] 97 | fn parse_repro_log(log: String, repro_log: String) -> Option { 98 | const FAILED: &str = "reproduction failed:"; 99 | let mut lines = repro_log.lines(); 100 | while let Some(l) = lines.next() { 101 | if l.rfind(FAILED).is_some() { 102 | return None; 103 | } 104 | 105 | if l.contains("opts: {") && l.contains("} crepro: ") { 106 | let mut opt_i = l.find("opts: ").unwrap(); 107 | opt_i += "opts ".len(); 108 | let mut repro_i = l.rfind("crepro: ").unwrap(); 109 | let opt = String::from(l[opt_i..repro_i].trim()); 110 | repro_i += "crepro:".len(); 111 | let crepro = String::from(l[repro_i..].trim()); 112 | let has_crepro = if crepro == "true" { 113 | true 114 | } else if crepro == "false" { 115 | false 116 | } else { 117 | continue; // bad line 118 | }; 119 | 120 | if let Some(sp) = lines.next() { 121 | if !sp.is_empty() { 122 | continue; 123 | } 124 | } else { 125 | break; 126 | } 127 | 128 | let mut p = String::new(); 129 | while let Some(l) = lines.next() { 130 | if l.is_empty() { 131 | break; 132 | } 133 | writeln!(p, "{}", l).unwrap(); 134 | } 135 | if p.is_empty() { 136 | break; 137 | } 138 | 139 | let c_prog = if has_crepro { 140 | let p = lines.map(|l| format!("{}\n", l)).collect::(); 141 | Some(p) 142 | } else { 143 | None 144 | }; 145 | 146 | return Some(ReproInfo { 147 | log, 148 | opt, 149 | p, 150 | c_prog, 151 | repro_log, 152 | }); 153 | } 154 | } 155 | None 156 | } 157 | 158 | fn build_log(target: &Target, history: &[(ExecOpt, Prog)], crash_log: &[u8]) -> Vec { 159 | let mut progs = String::new(); 160 | for (opt, p) in history.iter() { 161 | let mut opt_str = String::new(); 162 | if opt.flags & FLAG_INJECT_FAULT != 0 { 163 | opt_str = format!( 164 | " (fault-call:{} fault-nth:{})", 165 | opt.fault_call, opt.fault_nth 166 | ); 167 | } 168 | writeln!(progs, "executing program 0{}:", opt_str).unwrap(); 169 | writeln!(progs, "{}", p.display(target)).unwrap(); 170 | } 171 | 172 | let mut progs = progs.into_bytes(); 173 | progs.extend(crash_log); 174 | progs 175 | } 176 | 177 | fn syz_conf(conf: &ReproConfig) -> String { 178 | let conf = json!({ 179 | "target": &conf.target, 180 | "http": "127.0.0.1:65534", 181 | "workdir": &conf.work_dir, 182 | "image": &conf.disk_img, 183 | "sshkey": &conf.ssh_key, 184 | "syzkaller": &conf.syz_dir, 185 | "procs": 2, 186 | "type": "qemu", 187 | "vm": { 188 | "count": conf.qemu_count, 189 | "kernel": conf.kernel_img, 190 | "cpu": conf.qemu_smp, 191 | "mem": conf.qemu_mem, 192 | } 193 | }); 194 | 195 | // conf.to_string() 196 | to_string(&conf).unwrap() 197 | } 198 | -------------------------------------------------------------------------------- /syz_wrapper/src/report.rs: -------------------------------------------------------------------------------- 1 | use healer_core::prog::Prog; 2 | use std::{env::temp_dir, fmt::Write, fs::write, path::PathBuf, process::Command}; 3 | use thiserror::Error; 4 | 5 | #[derive(Default, Clone)] 6 | pub struct Report { 7 | pub title: String, 8 | pub corrupted: Option, 9 | pub to_mails: Vec, 10 | pub cc_mails: Vec, 11 | pub report: String, 12 | pub raw_log: Vec, 13 | pub prog: Option, 14 | } 15 | 16 | #[derive(Clone)] 17 | pub struct ReportConfig { 18 | pub os: String, 19 | pub arch: String, 20 | pub id: u64, 21 | pub syz_dir: String, 22 | pub kernel_obj_dir: Option, 23 | pub kernel_src_dir: Option, 24 | } 25 | 26 | impl Default for ReportConfig { 27 | fn default() -> Self { 28 | Self { 29 | os: "linux".to_string(), 30 | arch: "amd64".to_string(), 31 | id: 0, 32 | syz_dir: "./".to_string(), 33 | kernel_obj_dir: None, 34 | kernel_src_dir: None, 35 | } 36 | } 37 | } 38 | 39 | impl ReportConfig { 40 | pub fn check(&mut self) -> Result<(), String> { 41 | let syz_dir = PathBuf::from(&self.syz_dir); 42 | let syz_symbolize = syz_dir.join("bin").join("syz-symbolize"); 43 | if !syz_symbolize.exists() { 44 | return Err(format!("{} not exists", syz_symbolize.display())); 45 | } 46 | if let Some(dir) = self.kernel_obj_dir.as_ref() { 47 | let dir = PathBuf::from(dir); 48 | if !dir.is_dir() { 49 | return Err(format!("{} not exists", dir.display())); 50 | } 51 | } 52 | if let Some(dir) = self.kernel_src_dir.as_ref() { 53 | let dir = PathBuf::from(dir); 54 | if !dir.is_dir() { 55 | return Err(format!("{} not exists", dir.display())); 56 | } 57 | } 58 | Ok(()) 59 | } 60 | } 61 | 62 | #[derive(Debug, Error)] 63 | pub enum ReportError { 64 | #[error("io: {0}")] 65 | Io(#[from] std::io::Error), 66 | #[error("syz_symbolize: {0}")] 67 | SyzSymbolize(String), 68 | #[error("parse: {0}")] 69 | Parse(String), 70 | } 71 | 72 | pub fn extract_report( 73 | config: &ReportConfig, 74 | p: &Prog, 75 | raw_log: &[u8], 76 | ) -> Result, ReportError> { 77 | let log_file = temp_dir().join(format!("healer-crash-log-{}.tmp", config.id)); 78 | write(&log_file, raw_log)?; 79 | 80 | let syz_dir = PathBuf::from(&config.syz_dir); 81 | let mut syz_symbolize = Command::new(syz_dir.join("bin").join("syz-symbolize")); 82 | syz_symbolize 83 | .args(vec!["-os", &config.os]) 84 | .args(vec!["-arch", &config.arch]); 85 | if let Some(kernel_obj) = config.kernel_obj_dir.as_ref() { 86 | syz_symbolize.arg("-kernel_obj").arg(kernel_obj); 87 | } 88 | if let Some(kernel_src) = config.kernel_src_dir.as_ref() { 89 | syz_symbolize.arg("-kernel_src").arg(kernel_src); 90 | } 91 | syz_symbolize.arg(&log_file); 92 | let output = syz_symbolize.output().unwrap(); 93 | 94 | if output.status.success() { 95 | let content = String::from_utf8_lossy(&output.stdout).into_owned(); 96 | let mut ret = parse(&content); 97 | for r in ret.iter_mut() { 98 | r.prog = Some(p.clone()); 99 | r.raw_log = Vec::from(raw_log); 100 | } 101 | if ret.is_empty() { 102 | Err(ReportError::Parse(content)) 103 | } else { 104 | Ok(ret) 105 | } 106 | } else { 107 | let err = String::from_utf8_lossy(&output.stderr); 108 | Err(ReportError::SyzSymbolize(err.into_owned())) 109 | } 110 | } 111 | 112 | fn parse(content: &str) -> Vec { 113 | let mut ret = Vec::new(); 114 | let lines = &mut content.lines(); 115 | let mut last_title = None; 116 | 117 | loop { 118 | let mut title = None; 119 | if last_title.is_none() { 120 | for l in &mut *lines { 121 | if l.contains("TITLE:") { 122 | let l = l.trim(); 123 | title = Some(String::from(&l[7..])); 124 | break; 125 | } 126 | } 127 | } else { 128 | title = last_title.take(); 129 | } 130 | 131 | if title.is_none() { 132 | break; 133 | } 134 | 135 | let mut corrupted = None; 136 | for l in &mut *lines { 137 | if l.contains("CORRUPTED:") { 138 | if l.contains("true") { 139 | let idx = l.find('(').unwrap(); 140 | let mut corr = String::from(&l[idx + 1..]); 141 | corr.pop(); // drop ')' 142 | corrupted = Some(corr); 143 | } 144 | break; 145 | } 146 | } 147 | 148 | let mut to_mails = Vec::new(); 149 | for l in &mut *lines { 150 | if l.contains("MAINTAINERS (TO):") { 151 | let start = l.find('[').unwrap(); 152 | let end = l.rfind(']').unwrap(); 153 | if start + 1 != end { 154 | for mail in l[start + 1..end].split_ascii_whitespace() { 155 | to_mails.push(String::from(mail)); 156 | } 157 | } 158 | break; 159 | } 160 | } 161 | 162 | let mut cc_mails = Vec::new(); 163 | for l in &mut *lines { 164 | if l.contains("MAINTAINERS (CC):") { 165 | let start = l.find('[').unwrap(); 166 | let end = l.rfind(']').unwrap(); 167 | if start + 1 != end { 168 | for mail in l[start + 1..end].split_ascii_whitespace() { 169 | cc_mails.push(String::from(mail)); 170 | } 171 | } 172 | break; 173 | } 174 | } 175 | 176 | let mut report = String::new(); 177 | for l in &mut *lines { 178 | if l.contains("TITLE:") { 179 | let l = l.trim(); 180 | last_title = Some(String::from(&l[7..])); 181 | break; // next report 182 | } else { 183 | writeln!(report, "{}", l).unwrap(); 184 | } 185 | } 186 | 187 | ret.push(Report { 188 | title: title.unwrap(), 189 | corrupted, 190 | to_mails, 191 | cc_mails, 192 | report: report.trim().to_string(), 193 | ..Default::default() 194 | }); 195 | } 196 | ret 197 | } 198 | -------------------------------------------------------------------------------- /healer_core/src/gen/buffer.rs: -------------------------------------------------------------------------------- 1 | //! Generate value for `blob`, `string`, `filename` type. 2 | #![allow(clippy::unit_arg)] 3 | use rand::prelude::*; 4 | use std::ops::RangeInclusive; 5 | 6 | use crate::{ 7 | context::Context, 8 | gen::choose_weighted, 9 | ty::{BufferStringType, Dir, Type}, 10 | value::{DataValue, Value}, 11 | RngType, 12 | }; 13 | 14 | pub fn gen_buffer_blob(_ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 15 | let ty = ty.checked_as_buffer_blob(); 16 | let range = ty.range().unwrap_or_else(|| rand_blob_range(rng)); 17 | let len = rng.gen_range(range); 18 | if dir == Dir::Out { 19 | DataValue::new_out_data(ty.id(), dir, len).into() 20 | } else { 21 | DataValue::new(ty.id(), dir, rand_blob(rng, len as usize)).into() 22 | } 23 | } 24 | 25 | fn rand_blob_range(rng: &mut RngType) -> RangeInclusive { 26 | const LENS: [u64; 4] = [64, 128, 256, 4096]; 27 | const WEIGHTS: [u64; 4] = [60, 80, 95, 100]; 28 | let idx = choose_weighted(rng, &WEIGHTS); 29 | 0..=LENS[idx] 30 | } 31 | 32 | fn rand_blob(rng: &mut RngType, len: usize) -> Vec { 33 | let mut buf: Vec = Vec::with_capacity(len); 34 | // SAFETY: sizeof `buf` equals to `len`. 35 | unsafe { buf.set_len(len) }; 36 | let (prefix, shorts, suffix) = unsafe { buf.align_to_mut::() }; 37 | prefix.fill_with(|| rng.gen()); 38 | shorts.fill_with(|| rng.gen()); 39 | suffix.fill_with(|| rng.gen()); 40 | buf 41 | } 42 | 43 | pub fn gen_buffer_string(ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 44 | let ty = ty.checked_as_buffer_string(); 45 | if dir == Dir::Out { 46 | let len = if let Some(val) = ty.vals().choose(rng) { 47 | val.len() as u64 48 | } else { 49 | let r = rand_blob_range(rng); 50 | rng.gen_range(r) 51 | }; 52 | DataValue::new_out_data(ty.id(), dir, len).into() 53 | } else { 54 | let val = rand_buffer_string(ctx, rng, ty); 55 | DataValue::new(ty.id(), dir, val).into() 56 | } 57 | } 58 | 59 | fn rand_buffer_string(ctx: &mut Context, rng: &mut RngType, ty: &BufferStringType) -> Vec { 60 | if let Some(val) = ty.vals().choose(rng) { 61 | return Vec::from(&val[..]); 62 | } 63 | 64 | if !ctx.strs().is_empty() && rng.gen() { 65 | return ctx.strs().choose(rng).cloned().unwrap(); 66 | } 67 | 68 | let mut val = gen_rand_string(rng); 69 | if !ty.noz() { 70 | if val.is_empty() { 71 | val.push(0); 72 | } else { 73 | *val.last_mut().unwrap() = 0; 74 | } 75 | } 76 | if val.len() > 3 { 77 | ctx.record_str(val.clone()); 78 | } 79 | 80 | val 81 | } 82 | 83 | fn gen_rand_string(rng: &mut RngType) -> Vec { 84 | const PUNCT: [u8; 23] = [ 85 | b'!', b'@', b'#', b'$', b'%', b'^', b'&', b'*', b'(', b')', b'-', b'+', b'\\', b'/', b':', 86 | b'.', b',', b'-', b'\'', b'[', b']', b'{', b'}', 87 | ]; 88 | 89 | let r = rand_blob_range(rng); 90 | let len = rng.gen_range(r) as usize; 91 | let mut buf = Vec::with_capacity(len); 92 | unsafe { buf.set_len(len) }; 93 | for val in buf.iter_mut() { 94 | if rng.gen() { 95 | *val = PUNCT.choose(rng).copied().unwrap(); 96 | } else { 97 | *val = rng.gen_range(0..=255); 98 | } 99 | } 100 | buf 101 | } 102 | 103 | pub fn gen_buffer_filename(ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 104 | // let ty = ty.checked_as_buffer_filename(); 105 | if dir == Dir::Out { 106 | let len = if !ty.varlen() { 107 | ty.size() 108 | } else { 109 | rand_filename_len(rng) 110 | }; 111 | DataValue::new_out_data(ty.id(), dir, len).into() 112 | } else { 113 | let noz = if let Some(ty) = ty.as_buffer_string() { 114 | ty.noz() 115 | } else { 116 | let ty = ty.checked_as_buffer_filename(); 117 | ty.noz() 118 | }; 119 | let val = rand_filename(ctx, rng, ty, noz); 120 | DataValue::new(ty.id(), dir, val).into() 121 | } 122 | } 123 | pub const UNIX_PATH_MAX: u64 = 108; 124 | pub const PATH_MAX: u64 = 4096; 125 | 126 | #[inline] 127 | fn rand_filename_len(rng: &mut RngType) -> u64 { 128 | match rng.gen_range(0..=2) { 129 | 0 => rng.gen_range(0..100), 130 | 1 => UNIX_PATH_MAX, 131 | _ => PATH_MAX, 132 | } 133 | } 134 | 135 | pub(crate) fn rand_filename(ctx: &mut Context, rng: &mut RngType, ty: &Type, noz: bool) -> Vec { 136 | const GENERATORS: [fn(&mut Context, &mut RngType) -> Vec; 3] = 137 | [rand_reusing, gen_rand_filename, rand_speical_files]; 138 | const WEIGHTS: [u64; 3] = [80, 99, 100]; 139 | 140 | let idx = choose_weighted(rng, &WEIGHTS); 141 | let mut val = GENERATORS[idx](ctx, rng); 142 | // TODO clean filename 143 | if !ty.varlen() { 144 | let sz = ty.size() as usize; 145 | if val.len() != sz { 146 | val.resize(sz, 0); 147 | } 148 | } else if !noz { 149 | val.push(0); 150 | } 151 | val.shrink_to_fit(); 152 | val 153 | } 154 | 155 | fn rand_speical_files(_ctx: &mut Context, rng: &mut RngType) -> Vec { 156 | const SPEICAL_FILES: [&[u8]; 2] = [b"", b"."]; 157 | SPEICAL_FILES.choose(rng).map(|v| Vec::from(*v)).unwrap() 158 | } 159 | 160 | fn rand_reusing(ctx: &mut Context, rng: &mut RngType) -> Vec { 161 | if let Some(f) = ctx.filenames().choose(rng) { 162 | f.clone() 163 | } else { 164 | gen_rand_filename_inner(ctx, rng, None) 165 | } 166 | } 167 | 168 | fn gen_rand_filename(ctx: &mut Context, rng: &mut RngType) -> Vec { 169 | let base = if !ctx.filenames().is_empty() && rng.gen() { 170 | let mut base = ctx.filenames().choose(rng).cloned().unwrap(); 171 | if *base.last().unwrap() == 0 { 172 | base.pop(); 173 | } 174 | Some(base) 175 | } else { 176 | None 177 | }; 178 | gen_rand_filename_inner(ctx, rng, base) 179 | } 180 | 181 | fn gen_rand_filename_inner(ctx: &mut Context, rng: &mut RngType, base: Option>) -> Vec { 182 | let mut fname = vec![b'.']; 183 | if let Some(base) = base { 184 | if !base.is_empty() { 185 | fname = base; 186 | } 187 | } 188 | if fname.len() != 1 && fname[0] != b'.' && rng.gen_ratio(1, 10) { 189 | fname.extend_from_slice(b"/.."); 190 | } 191 | let mut i = 0; 192 | loop { 193 | fname.extend_from_slice(b"/file"); 194 | fname.extend_from_slice(i.to_string().as_bytes()); 195 | if ctx.record_filename(&fname) { 196 | return fname; 197 | } 198 | i += 1; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /healer_core/src/ty/common.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | hash::{Hash, Hasher}, 4 | }; 5 | 6 | use super::TypeId; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct CommonInfo { 10 | id: TypeId, // must be unique 11 | name: Box, 12 | size: u64, 13 | align: u64, 14 | optional: bool, 15 | varlen: bool, 16 | } 17 | 18 | impl CommonInfo { 19 | #[inline(always)] 20 | pub fn id(&self) -> TypeId { 21 | self.id 22 | } 23 | 24 | #[inline(always)] 25 | pub fn name(&self) -> &str { 26 | &self.name 27 | } 28 | 29 | #[inline(always)] 30 | pub fn size(&self) -> u64 { 31 | self.size 32 | } 33 | 34 | #[inline(always)] 35 | pub fn align(&self) -> u64 { 36 | self.align 37 | } 38 | 39 | #[inline(always)] 40 | pub fn optional(&self) -> bool { 41 | self.optional 42 | } 43 | 44 | #[inline(always)] 45 | pub fn varlen(&self) -> bool { 46 | self.varlen 47 | } 48 | 49 | pub fn template_name(&self) -> String { 50 | if let Some(idx) = self.name.find('[') { 51 | self.name[..idx].to_string() 52 | } else { 53 | String::from(&self.name[..]) 54 | } 55 | } 56 | } 57 | 58 | impl PartialEq for CommonInfo { 59 | fn eq(&self, other: &Self) -> bool { 60 | self.id == other.id 61 | } 62 | } 63 | 64 | impl Eq for CommonInfo {} 65 | 66 | impl PartialOrd for CommonInfo { 67 | fn partial_cmp(&self, other: &Self) -> Option { 68 | self.id.partial_cmp(&other.id) 69 | } 70 | } 71 | 72 | impl Ord for CommonInfo { 73 | fn cmp(&self, other: &Self) -> Ordering { 74 | self.id.cmp(&other.id) 75 | } 76 | } 77 | 78 | impl Hash for CommonInfo { 79 | fn hash(&self, hasher: &mut H) { 80 | self.id.hash(hasher) 81 | } 82 | } 83 | 84 | macro_rules! common_attr_getter { 85 | () => { 86 | #[inline(always)] 87 | pub fn id(&self) -> crate::ty::TypeId { 88 | self.comm.id() 89 | } 90 | 91 | #[inline(always)] 92 | pub fn name(&self) -> &str { 93 | &self.comm.name() 94 | } 95 | 96 | #[inline(always)] 97 | pub fn size(&self) -> u64 { 98 | self.comm.size() 99 | } 100 | 101 | #[inline(always)] 102 | pub fn align(&self) -> u64 { 103 | self.comm.align() 104 | } 105 | 106 | #[inline(always)] 107 | pub fn optional(&self) -> bool { 108 | self.comm.optional() 109 | } 110 | 111 | #[inline(always)] 112 | pub fn varlen(&self) -> bool { 113 | self.comm.varlen() 114 | } 115 | 116 | #[inline(always)] 117 | pub fn template_name(&self) -> String { 118 | self.comm.template_name() 119 | } 120 | 121 | #[inline(always)] 122 | pub fn comm(&self) -> &crate::ty::common::CommonInfo { 123 | &self.comm 124 | } 125 | }; 126 | } 127 | 128 | macro_rules! default_int_format_attr_getter { 129 | () => { 130 | #[inline(always)] 131 | pub fn bitfield_off(&self) -> u64 { 132 | 0 133 | } 134 | 135 | #[inline(always)] 136 | pub fn bitfield_len(&self) -> u64 { 137 | 0 138 | } 139 | 140 | #[inline(always)] 141 | pub fn bitfield_unit(&self) -> u64 { 142 | self.size() 143 | } 144 | 145 | #[inline(always)] 146 | pub fn bitfield_unit_off(&self) -> u64 { 147 | 0 148 | } 149 | 150 | #[inline(always)] 151 | pub fn is_bitfield(&self) -> bool { 152 | false 153 | } 154 | }; 155 | } 156 | 157 | macro_rules! extra_attr_getter { 158 | () => { 159 | #[inline(always)] 160 | pub fn bin_fmt(&self) -> crate::ty::BinaryFormat { 161 | crate::ty::BinaryFormat::Native 162 | } 163 | }; 164 | } 165 | 166 | macro_rules! eq_ord_hash_impl { 167 | ($impl_ty: ty) => { 168 | impl PartialEq for $impl_ty { 169 | fn eq(&self, other: &Self) -> bool { 170 | self.id() == other.id() 171 | } 172 | } 173 | 174 | impl Eq for $impl_ty {} 175 | 176 | impl PartialOrd for $impl_ty { 177 | fn partial_cmp(&self, other: &Self) -> Option { 178 | self.id().partial_cmp(&other.id()) 179 | } 180 | } 181 | 182 | impl Ord for $impl_ty { 183 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 184 | self.id().cmp(&other.id()) 185 | } 186 | } 187 | 188 | impl std::hash::Hash for $impl_ty { 189 | fn hash(&self, hasher: &mut H) { 190 | self.id().hash(hasher) 191 | } 192 | } 193 | }; 194 | } 195 | 196 | #[derive(Debug, Clone)] 197 | pub struct CommonInfoBuilder { 198 | id: TypeId, 199 | name: String, 200 | size: Option, 201 | align: Option, 202 | optional: bool, 203 | varlen: bool, 204 | } 205 | 206 | impl Default for CommonInfoBuilder { 207 | fn default() -> Self { 208 | Self::new() 209 | } 210 | } 211 | 212 | impl CommonInfoBuilder { 213 | pub fn new() -> Self { 214 | Self { 215 | name: String::new(), 216 | id: TypeId::MAX, 217 | size: None, 218 | align: None, 219 | optional: false, 220 | varlen: false, 221 | } 222 | } 223 | 224 | pub fn id(&mut self, id: TypeId) -> &mut Self { 225 | self.id = id; 226 | self 227 | } 228 | 229 | pub fn name>(&mut self, name: T) -> &mut Self { 230 | self.name = name.into(); 231 | self 232 | } 233 | 234 | pub fn size(&mut self, size: u64) -> &mut Self { 235 | self.size = Some(size); 236 | self 237 | } 238 | 239 | pub fn align(&mut self, align: u64) -> &mut Self { 240 | self.align = Some(align); 241 | self 242 | } 243 | 244 | pub fn optional(&mut self, optional: bool) -> &mut Self { 245 | self.optional = optional; 246 | self 247 | } 248 | 249 | pub fn varlen(&mut self, varlen: bool) -> &mut Self { 250 | self.varlen = varlen; 251 | self 252 | } 253 | 254 | pub fn build(self) -> CommonInfo { 255 | let size = self.size.unwrap_or_default(); 256 | let align = self.align.unwrap_or_default(); 257 | let varlen = self.size.is_none(); 258 | 259 | CommonInfo { 260 | id: self.id, 261 | name: self.name.into_boxed_str(), 262 | optional: self.optional, 263 | 264 | size, 265 | align, 266 | varlen, 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /healer_core/src/context.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::{ 4 | alloc::{Allocator, VmaAllocator}, 5 | prog::{Call, Prog}, 6 | relation::RelationWrapper, 7 | target::Target, 8 | ty::ResKind, 9 | value::ResValueId, 10 | HashMap, 11 | lang_mod::model::ModelWrapper, 12 | scheduler::Scheduler, 13 | }; 14 | 15 | /// A context records useful information of multiple calls. 16 | #[derive(Debug, Clone)] 17 | pub struct Context<'a, 'b> { 18 | /// Fuzzing target of current prog. 19 | pub(crate) target: &'a Target, 20 | /// Relations between syscalls. 21 | pub(crate) relation: &'b RelationWrapper, 22 | /// Lang_model to mold syscalls. 23 | pub(crate) model: &'b ModelWrapper, 24 | /// Scheduler to balance exploration&exploit. 25 | pub(crate) scheduler: &'b Scheduler, 26 | /// Dummy mem allocator. 27 | pub(crate) mem_allocator: Allocator, 28 | /// Dummy vma allocator. 29 | pub(crate) vma_allocator: VmaAllocator, 30 | /// Next avaliable resource id. 31 | pub(crate) next_res_id: u64, 32 | /// Generated res kind. 33 | pub(crate) res_kinds: Vec, 34 | /// Generated res kind&id mapping. 35 | pub(crate) res_ids: HashMap>, 36 | /// Generated strings. 37 | pub(crate) strs: Vec>, 38 | /// Generated filenames. 39 | pub(crate) filenames: Vec>, 40 | /// Calls of current context. 41 | pub(crate) calls: Vec, 42 | } 43 | 44 | impl<'a, 'b> Context<'a, 'b> { 45 | /// Create an empty context with `target` and `relation`. 46 | pub fn new(target: &'a Target, relation: &'b RelationWrapper, 47 | model: &'b ModelWrapper, scheduler: &'b Scheduler) -> Self { 48 | Self { 49 | target, 50 | relation, 51 | model, 52 | scheduler, 53 | mem_allocator: Allocator::new(target.mem_size()), 54 | vma_allocator: VmaAllocator::new(target.page_num()), 55 | next_res_id: 0, 56 | res_kinds: Vec::new(), 57 | res_ids: HashMap::new(), 58 | strs: Vec::new(), 59 | filenames: Vec::new(), 60 | calls: Vec::new(), 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | pub(crate) fn dummy() -> Self { 66 | let dummy = &0; 67 | let target: &'static Target = unsafe { std::mem::transmute(dummy) }; 68 | let relation: &'static RelationWrapper = unsafe { std::mem::transmute(dummy) }; 69 | let model: &'static ModelWrapper = unsafe { std::mem::transmute(dummy) }; 70 | let scheduler: &'static Scheduler = unsafe { std::mem::transmute(dummy) }; 71 | Self { 72 | target, 73 | relation, 74 | model, 75 | scheduler, 76 | mem_allocator: Allocator::new(1024), 77 | vma_allocator: VmaAllocator::new(1024), 78 | next_res_id: 0, 79 | res_kinds: Vec::new(), 80 | res_ids: HashMap::new(), 81 | strs: Vec::new(), 82 | filenames: Vec::new(), 83 | calls: Vec::new(), 84 | } 85 | } 86 | 87 | /// Get current target of context. 88 | #[inline(always)] 89 | pub fn target(&self) -> &'a Target { 90 | self.target 91 | } 92 | 93 | /// Get current relation of context. 94 | #[inline(always)] 95 | pub fn relation(&self) -> &RelationWrapper { 96 | self.relation 97 | } 98 | 99 | /// Get current model of context. 100 | #[inline(always)] 101 | pub fn model(&self) -> &ModelWrapper { 102 | self.model 103 | } 104 | 105 | /// Get current calls of context. 106 | #[inline(always)] 107 | pub fn calls(&self) -> &[Call] { 108 | &self.calls[..] 109 | } 110 | 111 | /// Get generated resource kinds of context. 112 | #[inline(always)] 113 | pub fn res(&self) -> &[ResKind] { 114 | &self.res_kinds 115 | } 116 | 117 | /// Get generated resource kind&name mapping of context. 118 | #[inline(always)] 119 | pub fn res_ids(&self) -> &HashMap> { 120 | &self.res_ids 121 | } 122 | 123 | #[inline(always)] 124 | pub fn strs(&self) -> &[Vec] { 125 | &self.strs 126 | } 127 | 128 | #[inline(always)] 129 | pub fn filenames(&self) -> &[Vec] { 130 | &self.filenames 131 | } 132 | 133 | /// Get mutable ref to current mem allocator. 134 | #[inline(always)] 135 | pub fn mem_allocator(&mut self) -> &mut Allocator { 136 | &mut self.mem_allocator 137 | } 138 | 139 | /// Get mutable ref to current vma allocator. 140 | #[inline(always)] 141 | pub fn vma_allocator(&mut self) -> &mut VmaAllocator { 142 | &mut self.vma_allocator 143 | } 144 | 145 | /// Append a call to context 146 | #[inline] 147 | pub fn append_call(&mut self, call: Call) { 148 | self.calls.push(call) 149 | } 150 | 151 | /// Next avaliable resource id. 152 | #[inline] 153 | pub fn next_res_id(&mut self) -> u64 { 154 | let id = self.next_res_id; 155 | self.next_res_id += 1; 156 | id 157 | } 158 | 159 | /// Record a generate resource to context. 160 | pub fn record_res(&mut self, kind: &ResKind, id: ResValueId) { 161 | if !self.res_ids.contains_key(kind) { 162 | self.res_ids.insert(kind.clone(), Vec::new()); 163 | self.res_kinds.push(kind.clone()); 164 | } 165 | self.res_ids.get_mut(kind).unwrap().push(id); 166 | } 167 | 168 | pub fn record_str(&mut self, val: Vec) { 169 | self.strs.push(val); 170 | } 171 | 172 | #[allow(clippy::ptr_arg)] // binary search requires &Vec 173 | pub fn record_filename(&mut self, val: &Vec) -> bool { 174 | if let Err(idx) = self.filenames.binary_search(val) { 175 | self.filenames.insert(idx, val.clone()); 176 | true 177 | } else { 178 | false 179 | } 180 | } 181 | 182 | /// Dump to prog. 183 | pub fn to_prog(self) -> Prog { 184 | Prog::new(self.calls) 185 | } 186 | } 187 | 188 | impl<'a, 'b> Display for Context<'a, 'b> { 189 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 190 | writeln!(f, "target: {}", self.target.target_name())?; 191 | writeln!(f, "relation num: {}", self.relation.num())?; 192 | writeln!(f, "mem: {:?}", self.mem_allocator)?; 193 | writeln!(f, "vma: {:?}", self.vma_allocator)?; 194 | writeln!(f, "res num: {}", self.next_res_id)?; 195 | writeln!(f, "res:")?; 196 | for (kind, ids) in &self.res_ids { 197 | writeln!(f, "\t{}: {:?}", kind, ids)?; 198 | } 199 | writeln!(f, "str:")?; 200 | for val in &self.strs { 201 | writeln!(f, "\t{:?}", val)?; 202 | } 203 | writeln!(f, "filenames:")?; 204 | for fname in &self.filenames { 205 | writeln!(f, "\t{:?}", fname)?; 206 | } 207 | writeln!(f, "calls:")?; 208 | for call in &self.calls { 209 | writeln!(f, "{}", call.display(self.target))? 210 | } 211 | Ok(()) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /healer_fuzzer/src/crash.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use healer_core::{target::Target, HashMap, HashSet}; 3 | use std::fmt::Write; 4 | use std::hash::Hash; 5 | use std::io::ErrorKind; 6 | use std::sync::atomic::{AtomicU64, Ordering}; 7 | use std::{ 8 | fs::{create_dir_all, write}, 9 | hash::Hasher, 10 | path::PathBuf, 11 | sync::Mutex, 12 | }; 13 | use syz_wrapper::{report::Report, repro::ReproInfo}; 14 | 15 | #[derive(Default)] 16 | pub struct CrashManager { 17 | out_dir: PathBuf, 18 | white_list: HashSet, 19 | raw_log_count: AtomicU64, 20 | reproducing: Mutex>, 21 | reports: Mutex>>, 22 | repros: Mutex>, 23 | } 24 | 25 | impl CrashManager { 26 | pub fn new(out_dir: PathBuf) -> Self { 27 | Self { 28 | out_dir, 29 | ..Self::default() 30 | } 31 | } 32 | 33 | pub fn with_whitelist(white_list: HashSet, out_dir: PathBuf) -> Self { 34 | Self { 35 | out_dir, 36 | white_list, 37 | raw_log_count: AtomicU64::new(0), 38 | reproducing: Mutex::new(HashSet::new()), 39 | reports: Mutex::new(HashMap::new()), 40 | repros: Mutex::new(HashMap::new()), 41 | } 42 | } 43 | 44 | pub fn save_raw_log(&self, crash_log: &[u8]) -> anyhow::Result { 45 | let count = self.raw_log_count.fetch_add(1, Ordering::Relaxed); 46 | if count >= 1024 { 47 | return Ok(false); 48 | } 49 | let out_dir = self.out_dir.join("crashes").join("raw_logs"); 50 | if let Err(e) = create_dir_all(&out_dir) { 51 | if e.kind() != ErrorKind::AlreadyExists { 52 | return Err(e).context("failed to create raw_logs dir"); 53 | } 54 | } 55 | let fname = out_dir.join(count.to_string()); 56 | write(&fname, &crash_log).context("failed to wrtie raw log")?; 57 | Ok(true) 58 | } 59 | 60 | pub fn save_new_report(&self, target: &Target, report: Report) -> anyhow::Result { 61 | let title = report.title.trim().to_string(); 62 | let mut id = None; 63 | { 64 | let mut reports = self.reports.lock().unwrap(); 65 | let entry = reports 66 | .entry(title.clone()) 67 | .or_insert_with(|| Vec::with_capacity(100)); 68 | if entry.len() < 100 { 69 | let mut r = report.clone(); 70 | if !entry.is_empty() { 71 | r.title.clear(); // do not save there info in mem, except for the first one. 72 | r.cc_mails.clear(); 73 | r.to_mails.clear(); 74 | r.raw_log.clear(); 75 | r.report.clear(); 76 | } 77 | entry.push(r); 78 | id = Some(entry.len()); 79 | } 80 | } 81 | if let Some(id) = id { 82 | self.save_report(target, report, id)?; 83 | } 84 | if self.white_list.contains(&title) { 85 | return Ok(false); 86 | } 87 | { 88 | let r = self.repros.lock().unwrap(); 89 | if r.contains_key(&title) { 90 | return Ok(false); 91 | } 92 | } 93 | let mut ri = self.reproducing.lock().unwrap(); 94 | Ok(ri.insert(title)) 95 | } 96 | 97 | pub fn unique_crashes(&self) -> u64 { 98 | let raw = (self.raw_log_count.load(Ordering::Relaxed) != 0) as u64; 99 | let n = self.reports.lock().unwrap(); 100 | n.len() as u64 + raw 101 | } 102 | 103 | fn save_report(&self, target: &Target, r: Report, id: usize) -> anyhow::Result<()> { 104 | let dir_name = Self::dir_name(&r.title); 105 | let out_dir = self.out_dir.join("crashes").join(&dir_name); 106 | if let Err(e) = create_dir_all(&out_dir) { 107 | if e.kind() != ErrorKind::AlreadyExists { 108 | return Err(e).context("failed to create report dir"); 109 | } 110 | } 111 | if id == 1 { 112 | let mut meta = String::new(); 113 | writeln!(meta, "TITLE: {}", r.title).unwrap(); 114 | if let Some(cor) = r.corrupted.as_ref() { 115 | writeln!(meta, "CORRUPTED: {}", cor).unwrap(); 116 | } 117 | if !r.to_mails.is_empty() { 118 | writeln!(meta, "MAINTAINERS (TO): {:?}", r.to_mails).unwrap(); 119 | } 120 | if !r.cc_mails.is_empty() { 121 | writeln!(meta, "MAINTAINERS (CC): {:?}", r.cc_mails).unwrap(); 122 | } 123 | write(out_dir.join("meta"), meta).context("failed to save report mate info")?; 124 | } 125 | write( 126 | out_dir.join(format!("prog{}", id)), 127 | r.prog.as_ref().unwrap().display(target).to_string(), 128 | ) 129 | .context("failed to write report prog")?; 130 | write(out_dir.join(format!("log{}", id)), r.raw_log) 131 | .context("failed to write report log")?; 132 | write(out_dir.join(format!("report{}", id)), r.report).context("failed to write report") 133 | } 134 | 135 | pub fn repro_done(&self, title: &str, repro: Option) -> anyhow::Result<()> { 136 | { 137 | let mut ri = self.reproducing.lock().unwrap(); 138 | ri.remove(title); 139 | } 140 | 141 | if let Some(repro) = repro { 142 | let mut save = false; 143 | { 144 | let mut repros = self.repros.lock().unwrap(); 145 | if !repros.contains_key(title) { 146 | repros.insert(title.to_string(), repro.clone()); 147 | save = true; 148 | } 149 | } 150 | if save { 151 | self.do_save_repro(title, repro)?; 152 | } 153 | } 154 | 155 | Ok(()) 156 | } 157 | 158 | fn do_save_repro(&self, title: &str, repro: ReproInfo) -> anyhow::Result<()> { 159 | let out_dir = self.out_dir.join("crashes").join(Self::dir_name(title)); 160 | let mut prog = format!("# {}\n\n", repro.opt); 161 | prog.push_str(&repro.p); 162 | let fname = out_dir.join("repro.prog"); 163 | write(&fname, prog.as_bytes()).context("failed to write repro.prog")?; 164 | let fname = out_dir.join("run_history"); 165 | write(&fname, repro.log.as_bytes()).context("failed to write run history")?; 166 | let fname = out_dir.join("repro.log"); 167 | write(&fname, repro.repro_log.as_bytes()).context("failed to write repro log")?; 168 | 169 | if let Some(cprog) = repro.c_prog.as_ref() { 170 | let fname = out_dir.join("repro.c"); 171 | write(&fname, cprog.as_bytes()).context("failed to write repro.c")?; 172 | } 173 | 174 | Ok(()) 175 | } 176 | 177 | fn dir_name(title: &str) -> String { 178 | let mut dir_name = title.replace('/', "~"); 179 | if dir_name.len() >= 255 { 180 | let mut hasher = ahash::AHasher::default(); 181 | dir_name.hash(&mut hasher); 182 | let hash = hasher.finish(); 183 | dir_name = format!("{:X}", hash); 184 | } 185 | dir_name 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /healer_core/src/gen/int.rs: -------------------------------------------------------------------------------- 1 | //! Generate value for `integer` like types. 2 | use crate::{ 3 | context::Context, 4 | gen::choose_weighted, 5 | ty::{Dir, Type}, 6 | value::{IntegerValue, Value}, 7 | RngType, 8 | }; 9 | use rand::prelude::*; 10 | use std::{cell::Cell, ops::RangeInclusive}; 11 | 12 | /// Generate value for `ConstType` 13 | #[inline] 14 | pub fn gen_const(_ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 15 | let ty = ty.checked_as_const(); 16 | let val = if rng.gen_ratio(1, 1000) { 17 | rng.gen() 18 | } else { 19 | ty.const_val() 20 | }; 21 | IntegerValue::new(ty.id(), dir, val).into() 22 | } 23 | 24 | pub fn gen_int(_ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 25 | let ty = ty.checked_as_int(); 26 | let bit_sz = ty.bit_size(); 27 | 28 | if let Some(range) = ty.range().cloned() { 29 | if rng.gen_ratio(99, 100) { 30 | let val = rand_int_range(rng, range, ty.align(), bit_sz); 31 | return IntegerValue::new(ty.id(), dir, val).into(); 32 | } 33 | } 34 | let val = rand_int_in_bit_sz(rng, bit_sz); 35 | IntegerValue::new(ty.id(), dir, val).into() 36 | } 37 | 38 | thread_local! { 39 | static NEED_CALCULATE_LEN: Cell = Cell::new(false); 40 | } 41 | 42 | #[inline] 43 | fn mark_len_calculation() { 44 | NEED_CALCULATE_LEN.with(|n| { 45 | n.set(true); 46 | }) 47 | } 48 | 49 | #[inline] 50 | pub(super) fn need_calculate_len() -> bool { 51 | NEED_CALCULATE_LEN.with(|n| n.get()) 52 | } 53 | 54 | #[inline] 55 | pub(super) fn len_calculated() { 56 | NEED_CALCULATE_LEN.with(|n| { 57 | n.set(false); 58 | }) 59 | } 60 | 61 | #[inline] 62 | pub fn gen_len(_ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 63 | mark_len_calculation(); // mark here, calculate latter. 64 | IntegerValue::new(ty.id(), dir, rng.gen()).into() 65 | } 66 | 67 | #[inline] 68 | pub fn gen_csum(_ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 69 | // calculated by executor 70 | IntegerValue::new(ty.id(), dir, rng.gen()).into() 71 | } 72 | 73 | #[inline] 74 | pub fn gen_proc(_ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 75 | let ty = ty.checked_as_proc(); 76 | IntegerValue::new(ty.id(), dir, rng.gen_range(0..ty.values_per_proc())).into() 77 | } 78 | 79 | pub fn gen_flags(_ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 80 | let ty = ty.checked_as_flags(); 81 | let val = if ty.bit_mask() { 82 | gen_flags_bitmask(rng, ty.vals(), 0) 83 | } else { 84 | gen_flags_non_bitmask(rng, ty.vals(), 0) 85 | }; 86 | IntegerValue::new(ty.id(), dir, val).into() 87 | } 88 | 89 | #[inline] 90 | pub fn gen_flags_bitmask(rng: &mut RngType, vals: &[u64], base: u64) -> u64 { 91 | if rng.gen_ratio(1, 100) { 92 | flag_rand_val(rng) 93 | } else { 94 | flags_bits_composition(rng, vals, base) 95 | } 96 | } 97 | 98 | #[inline] 99 | pub fn gen_flags_non_bitmask(rng: &mut RngType, vals: &[u64], base: u64) -> u64 { 100 | match rng.gen_range(0..100) { 101 | 0 => flag_rand_val(rng), 102 | 1..=2 if base != 0 => rand_inc(rng, base), 103 | 3..=97 => vals.choose(rng).copied().unwrap(), 104 | _ => flags_bits_composition(rng, vals, base), 105 | } 106 | } 107 | 108 | fn flags_bits_composition(rng: &mut RngType, vals: &[u64], mut base: u64) -> u64 { 109 | if base != 0 && rng.gen_ratio(1, 10) { 110 | base = 0; 111 | } 112 | let mut tries = 0; 113 | let max = std::cmp::min(10, vals.len()); 114 | while tries < max && (base == 0 || rng.gen_ratio(2, 3)) { 115 | let mut flag = vals.choose(rng).copied().unwrap(); 116 | if rng.gen_ratio(1, 20) { 117 | if rng.gen() { 118 | flag <<= 1; 119 | } else { 120 | flag >>= 1; 121 | } 122 | } 123 | base ^= flag; 124 | tries += 1; 125 | } 126 | base 127 | } 128 | 129 | fn rand_inc(rng: &mut RngType, mut base: u64) -> u64 { 130 | let mut inc = 1; 131 | if rng.gen() { 132 | inc ^= 0; 133 | } 134 | while rng.gen() { 135 | base = base.wrapping_add(inc); 136 | } 137 | base 138 | } 139 | 140 | #[inline] 141 | fn flag_rand_val(rng: &mut RngType) -> u64 { 142 | if rng.gen_ratio(3, 5) { 143 | rng.gen() 144 | } else { 145 | 0 146 | } 147 | } 148 | 149 | fn rand_int_range( 150 | rng: &mut RngType, 151 | mut range: RangeInclusive, 152 | align: u64, 153 | bit_sz: u64, 154 | ) -> u64 { 155 | if align != 0 { 156 | if *range.start() == 0 && *range.end() == u64::MAX { 157 | range = RangeInclusive::new(0, (1 << bit_sz) - 1); 158 | } 159 | let end_align = range.end().wrapping_sub(*range.start()) / align; 160 | range 161 | .start() 162 | .wrapping_add(rng.gen_range(0..=end_align) * align) 163 | } else { 164 | rng.gen_range(range) 165 | } 166 | } 167 | 168 | fn rand_int_in_bit_sz(rng: &mut RngType, bit_sz: u64) -> u64 { 169 | const GENERATORS: [fn(&mut RngType) -> u64; 3] = [favor_range, special_int, rand_int]; 170 | const WEIGHTS: [u64; 3] = [60, 90, 100]; 171 | let idx = choose_weighted(rng, &WEIGHTS); 172 | let mut val = GENERATORS[idx](rng); 173 | if bit_sz < 64 { 174 | val &= (1 << bit_sz) - 1 175 | } 176 | val 177 | } 178 | 179 | fn rand_int(rng: &mut RngType) -> u64 { 180 | rng.gen() 181 | } 182 | 183 | fn favor_range(rng: &mut RngType) -> u64 { 184 | const FAVOR: [u64; 5] = [16, 256, 4 << 10, 64 << 10, 1 << 31]; 185 | const WEIGHTS: [u64; 5] = [50, 70, 85, 95, 100]; 186 | let idx = choose_weighted(rng, &WEIGHTS); 187 | rng.gen_range(0..FAVOR[idx]) 188 | } 189 | 190 | const MAGIC32: [u64; 24] = [ 191 | 0, // 192 | 1, // 193 | 16, // One-off with common buffer size 194 | 32, // One-off with common buffer size 195 | 64, // One-off with common buffer size 196 | 100, // One-off with common buffer size 197 | 127, // Overflow signed 8-bit when incremented 198 | 128, // Overflow signed 8-bit when decremented 199 | 255, // -1 200 | 256, // Overflow unsig 8-bit 201 | 512, // One-off with common buffer size 202 | 1000, // One-off with common buffer size 203 | 1024, // One-off with common buffer size 204 | 4096, // One-off with common buffer size 205 | 32767, // Overflow signed 16-bit when incremented 206 | 32768, // Overflow signed 16-bit when decremented 207 | 65407, // Overflow signed 8-bit 208 | 65535, // Overflow unsig 16-bit when incremented 209 | 65536, // Overflow unsig 16 bit 210 | 100_663_045, // Large positive number (endian-agnostic) 211 | 2_147_483_647, // Overflow signed 32-bit when incremented 212 | 2_147_483_648, // Overflow signed 32-bit when decremented 213 | 4_194_304_250, // Large negative number (endian-agnostic) 214 | 4_294_934_527, // Overflow signed 16-bit 215 | ]; 216 | 217 | const MAGIC64: [u64; 24 * 24] = { 218 | let mut magic = [0; 24 * 24]; 219 | let mut i = 0; 220 | let mut j = 0; 221 | while i != 24 { 222 | while j != 24 { 223 | magic[i * 24 + j] = (MAGIC32[i] << 32) | MAGIC32[j]; 224 | j += 1; 225 | } 226 | i += 1; 227 | } 228 | magic 229 | }; 230 | 231 | #[inline] 232 | fn special_int(rng: &mut RngType) -> u64 { 233 | MAGIC64.choose(rng).copied().unwrap() 234 | } 235 | -------------------------------------------------------------------------------- /healer_core/src/gen/mod.rs: -------------------------------------------------------------------------------- 1 | //! Prog generation. 2 | use self::{ 3 | buffer::{gen_buffer_blob, gen_buffer_filename, gen_buffer_string}, 4 | group::{gen_array, gen_struct, gen_union}, 5 | int::{ 6 | gen_const, gen_csum, gen_flags, gen_int, gen_len, gen_proc, len_calculated, 7 | need_calculate_len, 8 | }, 9 | ptr::{gen_ptr, gen_vma}, 10 | res::gen_res, 11 | }; 12 | use crate::{ 13 | context::Context, 14 | len::calculate_len_args, 15 | mutation::fixup, 16 | prog::{CallBuilder, Prog}, 17 | relation::RelationWrapper, 18 | select::select, 19 | syscall::SyscallId, 20 | target::Target, 21 | ty::{Dir, Type, TypeKind}, 22 | value::{ResValue, Value}, 23 | RngType, 24 | lang_mod::model::ModelWrapper, scheduler::Scheduler, 25 | }; 26 | use rand::prelude::*; 27 | use std::{ 28 | cell::{Cell, RefCell}, 29 | ops::Range, 30 | }; 31 | 32 | pub mod buffer; 33 | pub mod group; 34 | pub mod int; 35 | pub mod ptr; 36 | pub mod res; 37 | 38 | pub const FAVORED_MIN_PROG_LEN: usize = 16; 39 | pub const FAVORED_MAX_PROG_LEN: usize = 25; 40 | 41 | thread_local! { 42 | static NEXT_PROG_LEN: Cell = Cell::new(FAVORED_MIN_PROG_LEN); 43 | static PROG_LEN_RANGE: Cell<(usize, usize)> = Cell::new((FAVORED_MIN_PROG_LEN, FAVORED_MAX_PROG_LEN)); 44 | } 45 | 46 | /// Set prog length range 47 | #[inline] 48 | pub fn set_prog_len_range(r: Range) { 49 | PROG_LEN_RANGE.with(|v| v.set((r.start, r.end))) 50 | } 51 | 52 | /// Get current prog length range 53 | #[inline] 54 | pub fn prog_len_range() -> Range { 55 | PROG_LEN_RANGE.with(|r| { 56 | let v = r.get(); 57 | Range { 58 | start: v.0, 59 | end: v.1, 60 | } 61 | }) 62 | } 63 | 64 | fn next_prog_len() -> usize { 65 | NEXT_PROG_LEN.with(|next_len| { 66 | let len = next_len.get(); 67 | let r = prog_len_range(); 68 | let mut new_len = len + 1; 69 | if new_len >= r.end { 70 | new_len = FAVORED_MIN_PROG_LEN 71 | }; 72 | next_len.set(new_len); 73 | len 74 | }) 75 | } 76 | 77 | /// Generate prog based on `target` and `relation`. 78 | pub fn gen_prog(target: &Target, relation: &RelationWrapper, model: &ModelWrapper, 79 | scheduler: &Scheduler, rng: &mut RngType) -> Prog { 80 | let mut ctx = Context::new(target, relation, model, scheduler); 81 | let len = next_prog_len(); 82 | debug_info!("prog len: {}", len); 83 | while ctx.calls().len() < len { 84 | gen_call(&mut ctx, rng); 85 | } 86 | debug_info!("Context:\n{}", ctx); 87 | fixup(target, &mut ctx.calls); 88 | ctx.to_prog() 89 | } 90 | 91 | /// Add a syscall to `context`. 92 | #[inline] 93 | pub fn gen_call(ctx: &mut Context, rng: &mut RngType) { 94 | let sid = select(ctx, rng); 95 | gen_one_call(ctx, rng, sid) 96 | } 97 | 98 | thread_local! { 99 | static CALLS_STACK: RefCell> = RefCell::new(Vec::with_capacity(4)); 100 | } 101 | 102 | #[inline] 103 | pub(crate) fn push_builder(sid: SyscallId) { 104 | CALLS_STACK.with(|calls| calls.borrow_mut().push(CallBuilder::new(sid))) 105 | } 106 | 107 | #[inline] 108 | pub(crate) fn current_builder(mut f: F) 109 | where 110 | F: FnMut(&mut CallBuilder), 111 | { 112 | CALLS_STACK.with(|calls| f(calls.borrow_mut().last_mut().unwrap())) 113 | } 114 | 115 | #[inline] 116 | pub(crate) fn pop_builder() -> CallBuilder { 117 | CALLS_STACK.with(|calls| calls.borrow_mut().pop().unwrap()) 118 | } 119 | 120 | /// Generate syscall `sid` to `context`. 121 | pub fn gen_one_call(ctx: &mut Context, rng: &mut RngType, sid: SyscallId) { 122 | push_builder(sid); 123 | debug_info!("generating: {}", ctx.target().syscall_of(sid)); 124 | let syscall = ctx.target().syscall_of(sid); 125 | let mut args = Vec::with_capacity(syscall.params().len()); 126 | for param in syscall.params() { 127 | args.push(gen_ty_value( 128 | ctx, 129 | rng, 130 | param.ty(), 131 | param.dir().unwrap_or(Dir::In), 132 | )); 133 | } 134 | if need_calculate_len() { 135 | calculate_len_args(ctx.target(), syscall.params(), &mut args); 136 | len_calculated(); 137 | } 138 | let ret = syscall.ret().map(|ty| { 139 | assert!(!ty.optional()); 140 | gen_ty_value(ctx, rng, ty, Dir::Out) 141 | }); 142 | 143 | let mut builder = pop_builder(); 144 | builder.args(args).ret(ret); 145 | ctx.append_call(builder.build()); 146 | } 147 | 148 | pub type Generator = fn(&mut Context, &mut RngType, &Type, Dir) -> Value; 149 | pub const GENERATOR: [Generator; 15] = [ 150 | gen_res, 151 | gen_const, 152 | gen_int, 153 | gen_flags, 154 | gen_len, 155 | gen_proc, 156 | gen_csum, 157 | gen_vma, 158 | gen_buffer_blob, 159 | gen_buffer_string, 160 | gen_buffer_filename, 161 | gen_array, 162 | gen_ptr, 163 | gen_struct, 164 | gen_union, 165 | ]; 166 | 167 | pub fn gen_ty_value(ctx: &mut Context, rng: &mut RngType, ty: &Type, dir: Dir) -> Value { 168 | use TypeKind::*; 169 | 170 | if dir == Dir::Out && matches!(ty.kind(), Const | Int | Flags | Proc | Vma) { 171 | ty.default_value(dir) 172 | } else if ty.optional() && rng.gen_ratio(1, 5) { 173 | if let Some(ty) = ty.as_res() { 174 | let v = ty.special_vals().choose(rng).copied().unwrap_or(0); 175 | return ResValue::new_null(ty.id(), dir, v).into(); 176 | } 177 | ty.default_value(dir) 178 | } else { 179 | GENERATOR[ty.kind() as usize](ctx, rng, ty, dir) 180 | } 181 | } 182 | 183 | /// Return chosen index based on `weights`. 184 | /// 185 | /// Weight is accumulated value. For example, [10, 35, 50] means each item has 186 | /// 10%, 25%, 15% to be chosen. 187 | pub(crate) fn choose_weighted(rng: &mut RngType, weights: &[u64]) -> usize { 188 | let max = weights.last().unwrap(); 189 | let n = rng.gen_range(0..*max); 190 | match weights.binary_search(&n) { 191 | Ok(idx) => idx + 1, 192 | Err(idx) => idx, 193 | } 194 | } 195 | 196 | pub fn minimize( 197 | target: &Target, 198 | p: &mut Prog, 199 | mut idx: usize, 200 | mut pred: impl FnMut(&Prog, usize) -> bool, 201 | ) -> usize { 202 | debug_assert!(!p.calls.is_empty()); 203 | debug_assert!(idx < p.calls.len()); 204 | 205 | for i in (0..p.calls.len()).rev() { 206 | if i == idx { 207 | continue; 208 | } 209 | let mut new_idx = idx; 210 | if i < idx { 211 | new_idx -= 1; 212 | } 213 | let new_p = p.remove_call(i); 214 | if !pred(&new_p, new_idx) { 215 | continue; 216 | } 217 | *p = new_p; 218 | idx = new_idx; 219 | } 220 | 221 | fixup(target, p.calls_mut()); 222 | 223 | // TODO Add args minimization 224 | 225 | idx 226 | } 227 | 228 | #[cfg(test)] 229 | mod tests { 230 | use rand::{prelude::SmallRng, SeedableRng}; 231 | 232 | #[test] 233 | fn next_prog_len() { 234 | assert_eq!(super::next_prog_len(), super::FAVORED_MIN_PROG_LEN); 235 | assert_eq!(super::next_prog_len(), super::FAVORED_MIN_PROG_LEN + 1); 236 | while super::next_prog_len() != super::FAVORED_MAX_PROG_LEN - 1 {} 237 | assert_eq!(super::next_prog_len(), super::FAVORED_MIN_PROG_LEN); 238 | } 239 | 240 | #[test] 241 | fn choose_weighted() { 242 | let mut rng = SmallRng::from_entropy(); 243 | let weight = [100]; 244 | assert_eq!(super::choose_weighted(&mut rng, &weight), 0); 245 | let weights = [10, 20, 100]; 246 | for _ in 0..10 { 247 | let idx = super::choose_weighted(&mut rng, &weight); 248 | assert!(idx < weights.len()); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /healer_core/src/relation.rs: -------------------------------------------------------------------------------- 1 | //! Relation learning algorithm. 2 | 3 | use std::sync::RwLock; 4 | use std::io::Write; 5 | use std::path::Path; 6 | use std::fs::File; 7 | 8 | use crate::{syscall::SyscallId, target::Target, HashMap}; 9 | // use crate::{prog::Prog}; 10 | 11 | #[derive(Debug)] 12 | pub struct RelationWrapper { 13 | pub inner: RwLock, 14 | } 15 | 16 | impl RelationWrapper { 17 | pub fn new(r: Relation) -> Self { 18 | Self { 19 | inner: RwLock::new(r), 20 | } 21 | } 22 | 23 | // pub fn try_update(&self, p: &Prog, idx: usize, pred: T) -> bool 24 | // where 25 | // T: FnMut(&Prog, usize) -> bool, // fn(new_prog: &Prog, index: usize) -> bool 26 | // { 27 | // let mut inner = self.inner.write().unwrap(); 28 | // inner.try_update(p, idx, pred) 29 | // } 30 | 31 | /// Return if `a` can influence the execution of `b`. 32 | #[inline] 33 | pub fn influence(&self, a: SyscallId, b: SyscallId) -> bool { 34 | let inner = self.inner.read().unwrap(); 35 | inner.influence(a, b) 36 | } 37 | 38 | /// Return if `a` can be influenced by the execution of `b`. 39 | #[inline] 40 | pub fn influence_by(&self, a: SyscallId, b: SyscallId) -> bool { 41 | let inner = self.inner.read().unwrap(); 42 | inner.influence_by(a, b) 43 | } 44 | 45 | /// Return the number of known relations. 46 | #[inline(always)] 47 | pub fn num(&self) -> usize { 48 | let inner = self.inner.read().unwrap(); 49 | inner.num() 50 | } 51 | 52 | // Save the relation table 53 | pub fn dump(&self, out: &Path) -> bool { 54 | let inner = self.inner.read().unwrap(); 55 | inner.dump(out) 56 | } 57 | 58 | } 59 | 60 | /// Influence relations between syscalls. 61 | #[derive(Debug, Clone)] 62 | pub struct Relation { 63 | influence: HashMap>, 64 | influence_by: HashMap>, 65 | n: usize, 66 | } 67 | 68 | impl Relation { 69 | /// Create initial relations based on syscall type information. 70 | pub fn new(target: &Target) -> Self { 71 | let influence: HashMap> = target 72 | .enabled_syscalls() 73 | .iter() 74 | .map(|syscall| (syscall.id(), Vec::new())) 75 | .collect(); 76 | let influence_by = influence.clone(); 77 | let mut r = Relation { 78 | influence, 79 | influence_by, 80 | n: 0, 81 | }; 82 | 83 | for i in target.enabled_syscalls().iter().map(|s| s.id()) { 84 | for j in target.enabled_syscalls().iter().map(|s| s.id()) { 85 | if i != j && Self::calculate_influence(target, i, j) { 86 | r.push_ordered(i, j); 87 | } 88 | } 89 | } 90 | 91 | r 92 | } 93 | 94 | /// Calculate if syscall `a` can influence the execution of syscall `b` based on 95 | /// input/output resources. 96 | /// 97 | /// Syscall `a` can influcen syscall `b` when any resource output by `a` is subtype 98 | /// of resources input by `b`. For example, syscall `a` outputs resource `sock`, syscall 99 | /// `b` takes resource `fd` as input, then `a` can influence `b`, because `sock` is 100 | /// subtype of `fd`. In contrast, if `b` takes `sock_ax25` as input, then the above 101 | /// conlusion maybe wrong (return false), because `sock` is not subtype of `sock_ax25` and 102 | /// the output resource of `a` maybe useless for `b`. For the latter case, the relation 103 | /// should be judged with dynamic method. 104 | pub fn calculate_influence(target: &Target, a: SyscallId, b: SyscallId) -> bool { 105 | let output_res_a = target.syscall_output_res(a); 106 | let input_res_b = target.syscall_input_res(b); 107 | 108 | !output_res_a.is_empty() 109 | && !input_res_b.is_empty() 110 | && input_res_b.iter().any(|input_res| { 111 | output_res_a 112 | .iter() 113 | .any(|output_res| target.res_sub_tys(input_res).contains(output_res)) 114 | }) 115 | } 116 | 117 | /// Detect relations by removing calls dynamically. 118 | /// 119 | /// The algorithm removes call before call `idx `of `p` and calls the callback 120 | /// `changed` to verify if the removal changed the feedback of adjacent call. 121 | /// For example, for prog [open, read], the algorithm removes `open` first and calls `changed` 122 | /// with the index of `open` (0 in this case) and the `new_prog`. The index of `open` equals to 123 | /// the index of `read` in the new `prog` and the callback `changed` should judge the feedback 124 | /// changes of the `index` call after the execution of `new_prog`. Finally, `try_update` returns 125 | /// the number of detected new relations. 126 | // pub fn try_update(&mut self, p: &Prog, idx: usize, mut pred: T) -> bool 127 | // where 128 | // T: FnMut(&Prog, usize) -> bool, // fn(new_prog: &Prog, index: usize) -> bool 129 | // { 130 | // let mut found_new = false; 131 | // if idx == 0 { 132 | // return found_new; 133 | // } 134 | // let a = &p.calls[idx - 1]; 135 | // let b = &p.calls[idx]; 136 | // if !self.influence(a.sid(), b.sid()) { 137 | // let new_p = p.remove_call(idx - 1); 138 | // if pred(&new_p, idx - 1) { 139 | // self.push_ordered(a.sid(), b.sid()); 140 | // found_new = true; 141 | // } 142 | // } 143 | 144 | // found_new 145 | // } 146 | 147 | /// Return if `a` can influence the execution of `b`. 148 | #[inline] 149 | pub fn influence(&self, a: SyscallId, b: SyscallId) -> bool { 150 | self.influence[&a].binary_search(&b).is_ok() 151 | } 152 | 153 | /// Return if `a` can be influenced by the execution of `b`. 154 | #[inline] 155 | pub fn influence_by(&self, a: SyscallId, b: SyscallId) -> bool { 156 | self.influence_by[&a].binary_search(&b).is_ok() 157 | } 158 | 159 | /// Return the known syscalls that `a` can influence. 160 | #[inline] 161 | pub fn influence_of(&self, a: SyscallId) -> &[SyscallId] { 162 | &self.influence[&a] 163 | } 164 | 165 | /// Return the known syscalls that can influence `a`. 166 | #[inline] 167 | pub fn influence_by_of(&self, a: SyscallId) -> &[SyscallId] { 168 | &self.influence_by[&a] 169 | } 170 | 171 | #[inline(always)] 172 | pub fn influences(&self) -> &HashMap> { 173 | &self.influence 174 | } 175 | 176 | #[inline(always)] 177 | pub fn influences_by(&self) -> &HashMap> { 178 | &self.influence_by 179 | } 180 | 181 | /// Return the number of known relations. 182 | #[inline(always)] 183 | pub fn num(&self) -> usize { 184 | self.n 185 | } 186 | 187 | pub fn insert(&mut self, a: SyscallId, b: SyscallId) -> bool { 188 | let old = self.num(); 189 | self.push_ordered(a, b); 190 | old != self.num() 191 | } 192 | 193 | fn push_ordered(&mut self, a: SyscallId, b: SyscallId) { 194 | let rs_a = self.influence.get_mut(&a).unwrap(); 195 | if let Err(idx) = rs_a.binary_search(&b) { 196 | self.n += 1; 197 | rs_a.insert(idx, b); 198 | } 199 | let rs_b = self.influence_by.get_mut(&b).unwrap(); 200 | if let Err(idx) = rs_b.binary_search(&a) { 201 | rs_b.insert(idx, a); 202 | } 203 | } 204 | 205 | pub fn dump(&self, out: &Path) -> bool { 206 | let mut file = File::create(out).unwrap(); 207 | self.influence.iter().for_each(|(id1, id_vec)| { 208 | write!(file, "{}", &id1).unwrap(); 209 | for id2 in id_vec { 210 | write!(file, " {}", id2).unwrap(); 211 | } 212 | write!(file, "\n").unwrap(); 213 | }); 214 | true 215 | } 216 | } 217 | --------------------------------------------------------------------------------