diff --git a/driver/CMakeLists.txt b/driver/CMakeLists.txt index 0a2b4c0..54fcb45 100644 --- a/driver/CMakeLists.txt +++ b/driver/CMakeLists.txt @@ -18,6 +18,25 @@ target_link_libraries(FGTest PRIVATE ) install (TARGETS FGTest DESTINATION ${SYMSAN_BIN_DIR}) +## RGD/JIGSAW-backed one-shot driver (SOTA solver cascade: I2S -> JIGSAW -> Z3) +add_executable(FGTestRGD fgtest_rgd.cpp) +set_target_properties(FGTestRGD PROPERTIES OUTPUT_NAME "fgtest_rgd") +target_compile_options(FGTestRGD PRIVATE -mcx16 -march=native) +target_include_directories(FGTestRGD PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../runtime +) +target_link_libraries(FGTestRGD PRIVATE + launcher + rgd-parser + rgd-solver + jigsaw + ${Z3_LIBRARY} + tcmalloc + profiler + rt +) +install (TARGETS FGTestRGD DESTINATION ${SYMSAN_BIN_DIR}) + if (DEFINED AFLPP_PATH) add_subdirectory(aflpp) endif() diff --git a/driver/fgtest.cpp b/driver/fgtest.cpp index 510f5a1..eebdac6 100644 --- a/driver/fgtest.cpp +++ b/driver/fgtest.cpp @@ -11,7 +11,10 @@ extern "C" { #include "parse-z3.h" #include +#include +#include #include +#include #include #include #include @@ -43,6 +46,7 @@ static size_t input_size; // for output static const char* __output_dir = "."; +static char* __focus_spec = nullptr; // 选择性符号化区间规格,待 symsan_init 之后下发 static uint32_t __instance_id = 0; static uint32_t __session_id = 0; static uint32_t __current_index = 0; @@ -52,6 +56,99 @@ static z3::context __z3_context; // z3parser symsan::Z3ParserSolver *__z3_parser = nullptr; +// ============ 技术③ 字典引导 + 技术① 多字段解组合 ============ +// 均移植自 SymCC 的 qsym solver.cpp,在 driver 层实现(不触碰 DFSan 运行时/rgd 求解器)。 +// 经 getenv 读 SYMCC_DICT / SYMCC_MULTI_SOLVE——与 SymCC 同名通道,编排层的策略档已下发, +// SymSanEngine.wrap_run 原样透传环境变量给 fgtest,故无需额外接线。 +static std::vector> __dict_tokens; // ③ AFL 字典 token 列表 +static bool __multi_solve = false; // ① SYMCC_MULTI_SOLVE>=1 时启用 +static std::map __combined_sets; // ① 累积各分支的 SET 解(offset->val) +static bool __emit_hints = false; // ② SYMCC_EMIT_HINTS 时写 .hints 旁车 + +// 把一段字节写成一个编号 id-* 输出(求解结果 / dict 变体 / combined 输入复用同一出口), +// 返回写出的文件路径(空串=失败),供 ② hint 旁车文件复用同一文件名。 +static std::string write_output(const std::vector& data, const char* tag) { + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "%s/id-%d-%d-%d", __output_dir, + __instance_id, __session_id, __current_index++); + int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { AOUT("failed to open %s output\n", tag); return std::string(); } + if (write(fd, data.data(), data.size()) == -1) + AOUT("failed to write %s output\n", tag); + close(fd); + return std::string(path); +} + +// ② hint 传递(移植自 SymCC saveValues 的 emit_hints 段):给某个求解输出写 .hints 旁车, +// 每行 "offset:old:new"(十六进制)记录相对原始种子改动的字节。编排层 +// (mpi_fuzzing_helper 读 *.hints → hint_map)据此做精准变异 / focus_bytes。 +static void emit_hints(const std::string& path, const std::vector& out) { + if (!__emit_hints || path.empty()) return; + std::ofstream hf(path + ".hints"); + if (!hf.is_open()) return; + char buf[64]; + for (size_t i = 0; i < out.size() && i < input_size; i++) { + if (out[i] != (uint8_t)input_buf[i]) { + snprintf(buf, sizeof(buf), "%zu:%02x:%02x\n", i, + (uint8_t)input_buf[i], out[i]); + hf << buf; + } + } +} + +// ③ 载入 AFL 字典(SYMCC_DICT):格式 "token" 或 name="token",支持 \xNN 转义。 +// 复刻 SymCC Solver::loadDictionary。 +static void load_dictionary() { + const char* dict_path = getenv("SYMCC_DICT"); + if (!dict_path) return; + std::ifstream ifs(dict_path); + if (ifs.fail()) { AOUT("cannot open dict %s\n", dict_path); return; } + std::string line; + while (std::getline(ifs, line)) { + size_t s = line.find('"'), e = line.rfind('"'); + if (s == std::string::npos || e == std::string::npos || s >= e) continue; + std::string t = line.substr(s + 1, e - s - 1); + std::vector token; + for (size_t i = 0; i < t.size(); i++) { + if (t[i] == '\\' && i + 3 < t.size() && t[i + 1] == 'x') { + char hex[3] = {t[i + 2], t[i + 3], '\0'}; + token.push_back((uint8_t)strtol(hex, NULL, 16)); + i += 3; + } else { + token.push_back((uint8_t)t[i]); + } + } + if (!token.empty()) __dict_tokens.push_back(token); + } + AOUT("loaded %zu dict tokens from %s\n", __dict_tokens.size(), dict_path); +} + +// ③ 对 Z3 求解改动的【首个】字节位置,用字典 token 拼接生成额外变体(cap 20)。 +// 从原始种子出发(非求解结果),跳过与原值/解相同的 token。复刻 SymCC saveDictVariants。 +static void save_dict_variants(const std::vector& solved) { + if (__dict_tokens.empty()) return; + size_t first = SIZE_MAX; + for (size_t i = 0; i < solved.size() && i < input_size; i++) + if (solved[i] != (uint8_t)input_buf[i]) { first = i; break; } + if (first == SIZE_MAX) return; + int variants = 0; + const int kMaxDictVariants = 20; + for (const auto& token : __dict_tokens) { + if (variants >= kMaxDictVariants) break; + if (first + token.size() > input_size) continue; + bool same_orig = true, same_solved = true; + for (size_t j = 0; j < token.size(); j++) { + if (token[j] != (uint8_t)input_buf[first + j]) same_orig = false; + if (first + j < solved.size() && token[j] != solved[first + j]) same_solved = false; + } + if (same_orig || same_solved) continue; + std::vector variant(input_buf, input_buf + input_size); + for (size_t j = 0; j < token.size(); j++) variant[first + j] = token[j]; + write_output(variant, "dict"); + variants++; + } +} + static void generate_input(symsan::Z3ParserSolver::solution_t &solutions) { using op_t = symsan::Z3ParserSolver::solution_op_t; @@ -96,24 +193,37 @@ static void generate_input(symsan::Z3ParserSolver::solution_t &solutions) { } } - // Write the new input to file - char path[PATH_MAX]; - snprintf(path, PATH_MAX, "%s/id-%d-%d-%d", __output_dir, - __instance_id, __session_id, __current_index++); - int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR); - if (fd == -1) { - AOUT("failed to open new input file for write"); - return; + // ① 多字段解组合:累积本分支的 SET 解(offset->val,后写覆盖)。运行结束时若累积到 >=2 + // 个不同偏移,额外产出一个"同时满足全部字段"的组合输入(见 flush_combined_input)。 + if (__multi_solve) { + for (const auto& sol : solutions) + if (sol.op == op_t::SET && sol.offset < input_size) + __combined_sets[sol.offset] = sol.val; } + // Write the new input to file AOUT("generate #%d output (size: %zu -> %zu)\n", - __current_index - 1, input_size, new_input.size()); + __current_index, input_size, new_input.size()); + std::string path = write_output(new_input, "sol"); + emit_hints(path, new_input); // ② 写 offset:old:new 旁车,供编排层精准变异 - if (write(fd, new_input.data(), new_input.size()) == -1) { - AOUT("failed to write new input\n"); - } + // ③ 字典引导:对本次求解改动的首个位置拼接字典 token,产出额外变体 + save_dict_variants(new_input); +} - close(fd); +// ① 运行结束时把累积的各分支 SET 解合并成一个输入(多字段一次全满足)。SymSan 的 +// 求解器按 task 逐分支求解,故这里在【解层面】组合,达到 SymCC negateGroup 联合求解 +// 同样的效果:一次跨过多字段校验,省去逐字段反馈迭代。各偏移互斥 → 组合无冲突;结果 +// 是候选输入,由覆盖反馈确认(sound)。 +static void flush_combined_input() { + if (!__multi_solve || __combined_sets.size() < 2) return; + std::vector merged(input_buf, input_buf + input_size); + for (const auto& kv : __combined_sets) + if (kv.first < merged.size()) merged[kv.first] = kv.second; + AOUT("multi-solve: combined %zu field solutions into one input\n", + __combined_sets.size()); + std::string path = write_output(merged, "combined"); + emit_hints(path, merged); // ② 组合输出同样写 hint 旁车 } static void __solve_cond(dfsan_label label, uint8_t r, bool add_nested, void *addr) { @@ -204,6 +314,18 @@ int main(int argc, char* const argv[]) { is_stdin = 1; } + // 选择性符号化:解析 focus_bytes(如 "0-3" 或 "0-3,8-11")。注意 symsan_init 会 + // 重置 g_config,故此处仅把规格暂存到 fgtest 自己的静态变量,待 init 之后再下发。 + char *focus = strstr(options, "focus_bytes="); + if (focus) { + focus += strlen("focus_bytes="); + char *end = strchr(focus, ':'); + if (end == NULL) end = strchr(focus, ' '); + size_t n = end == NULL ? strlen(focus) : (size_t)(end - focus); + if (n > 0) + __focus_spec = strndup(focus, n); + } + // check for debug char *debug_opt = strstr(options, "debug="); if (debug_opt) { @@ -236,6 +358,11 @@ int main(int argc, char* const argv[]) { } } + // 技术③/②/① 开关(与 SymCC 同名环境变量,经 fgtest 自身 env 直接读取): + __multi_solve = getenv("SYMCC_MULTI_SOLVE") != nullptr; // ① 多字段解组合 + __emit_hints = getenv("SYMCC_EMIT_HINTS") != nullptr; // ② hint 旁车文件 + load_dictionary(); // ③ 载入 SYMCC_DICT 字典 + // load input file struct stat st; int input_fd = open(input, O_RDONLY); @@ -273,8 +400,14 @@ int main(int argc, char* const argv[]) { } symsan_set_debug(debug); - symsan_set_bounds_check(1); + // "遇内存错误即退出"纯为内存错误检测,concolic 求解不需要,反而有害:目标合法的未初始化 + // 内存访问会在 bounds/GEP 追踪里产生 kInitializingLabel → 提前 Die() → 丢大量产出。实测默认关掉后 + // 各目标严格更优(base64 28→56、pcre2 140→488、xml 不变、sqlite 0→42)。故【默认关】, + // SYMSAN_MEMERR_EXIT=1 可恢复。bounds 追踪保留(供 GEP/数组下标求解);SYMSAN_NO_BOUNDS 可关。 + symsan_set_bounds_check(getenv("SYMSAN_NO_BOUNDS") ? 0 : 1); + symsan_set_exit_on_memerror(getenv("SYMSAN_MEMERR_EXIT") ? 1 : 0); symsan_set_solve_ub(solve_ub); + symsan_set_focus_bytes(__focus_spec); // 选择性符号化(init 之后下发,避免被重置) // launch the target int ret = symsan_run(input_fd); @@ -355,6 +488,8 @@ int main(int argc, char* const argv[]) { } } + flush_combined_input(); // ① 事件循环结束后产出多字段组合输入 + symsan_destroy(); exit(0); } diff --git a/driver/fgtest_rgd.cpp b/driver/fgtest_rgd.cpp new file mode 100644 index 0000000..4420c59 --- /dev/null +++ b/driver/fgtest_rgd.cpp @@ -0,0 +1,363 @@ +// fgtest_rgd:RGD/JIGSAW 后端的 one-shot concolic driver。 +// +// 与 fgtest.cpp(进程内 Z3,Z3ParserSolver)的区别:改用 SymSan 的 SOTA 求解栈 +// (RGDAstParser + I2SSolver→(JITSolver)→Z3Solver 级联,源自 R-Fuzz/SymSan 的 AFL++ +// custom-mutator 参考实现 driver/aflpp/symsan.cpp)。契约相同:读一个种子文件、跑目标、 +// 把每个求出的新输入写进 TAINT_OPTIONS 的 output_dir/id-*,故编排层可原样复用。 +// +// 求解级联(每个 task 依次尝试,命中即止): +// I2SSolver —— input-to-state(RedQueen 式),直接把比较操作数回写到输入,cheap,专治 +// magic/校验和; +// JITSolver —— JIGSAW 梯度求解(可选,SYMSAN_USE_JIGSAW;需 LLVM JIT); +// Z3Solver —— SMT 兜底,保证不弱于 fgtest 的 Z3 基线。 +// +// 复用移植的自研技术:④选择性符号化(运行时 get_label_for 门控,经 focus_bytes 透传)、 +// ③字典引导(SYMCC_DICT)、②hint(SYMCC_EMIT_HINTS)。①多字段解组合对"整缓冲输出"的 +// RGD 解意义不大(I2S 本就整体求解),此 driver 不含。 +#include "defs.h" +#include "debug.h" +#include "version.h" + +#include "dfsan/dfsan.h" + +extern "C" { +#include "launch.h" +} + +#include "parse-rgd.h" +#include "cov.h" +#include "task_mgr.h" +#include "solver.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace __dfsan; + +#undef AOUT +#define AOUT(...) \ + do { \ + if (__debug) printf(__VA_ARGS__); \ + } while (false) + +// ---- input / output 全局 ---- +static char *input_buf; +static size_t input_size; +static const char *__output_dir = "."; +static char *__focus_spec = nullptr; // ④ 待 symsan_init 之后下发 +static uint32_t __instance_id = 0; +static uint32_t __session_id = 0; +static uint32_t __current_index = 0; +static int __debug = 0; +static uint8_t *__out_buf = nullptr; // RGD 求解器写整个新输入到此 +static const size_t kMaxOut = 1u << 20; // 1 MiB 输出上界 + +// ---- 技术③/② 全局 ---- +static std::vector> __dict_tokens; // ③ AFL 字典 +static bool __emit_hints = false; // ② .hints 旁车 + +// ---- RGD 求解栈 ---- +// 求解器(i2s)需要 __dfsan::get_label_info 索引 shm 里的 union table;driver 侧提供 +// (与 aflpp/symsan.cpp 一致)。base 在 symsan_init 之后设。 +static dfsan_label_info *__label_info_base = nullptr; +static size_t __max_label = 0; +namespace __dfsan { +dfsan_label_info *get_label_info(dfsan_label label) { + if (label >= __max_label) + throw std::out_of_range("label too large " + std::to_string(label)); + return &__label_info_base[label]; +} +} // namespace __dfsan + +static rgd::RGDAstParser *__parser = nullptr; +static rgd::TaskManager *__task_mgr = nullptr; +static rgd::CovManager *__cov_mgr = nullptr; +static std::vector> __solvers; + +// 把一段字节写成一个编号 id-* 输出,返回路径(空=失败),供 ② hint 旁车复用文件名。 +static std::string write_output(const uint8_t *data, size_t len, const char *tag) { + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "%s/id-%d-%d-%d", __output_dir, + __instance_id, __session_id, __current_index++); + int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { AOUT("failed to open %s output\n", tag); return std::string(); } + if (write(fd, data, len) == -1) + AOUT("failed to write %s output\n", tag); + close(fd); + return std::string(path); +} + +// ② hint:写 offset:old:new 旁车(相对种子的改动字节) +static void emit_hints(const std::string &path, const uint8_t *out, size_t len) { + if (!__emit_hints || path.empty()) return; + std::ofstream hf(path + ".hints"); + if (!hf.is_open()) return; + char buf[64]; + for (size_t i = 0; i < len && i < input_size; i++) { + if (out[i] != (uint8_t)input_buf[i]) { + snprintf(buf, sizeof(buf), "%zu:%02x:%02x\n", i, (uint8_t)input_buf[i], out[i]); + hf << buf; + } + } +} + +// ③ 载入 AFL 字典(SYMCC_DICT) +static void load_dictionary() { + const char *dict_path = getenv("SYMCC_DICT"); + if (!dict_path) return; + std::ifstream ifs(dict_path); + if (ifs.fail()) return; + std::string line; + while (std::getline(ifs, line)) { + size_t s = line.find('"'), e = line.rfind('"'); + if (s == std::string::npos || e == std::string::npos || s >= e) continue; + std::string t = line.substr(s + 1, e - s - 1); + std::vector token; + for (size_t i = 0; i < t.size(); i++) { + if (t[i] == '\\' && i + 3 < t.size() && t[i + 1] == 'x') { + char hex[3] = {t[i + 2], t[i + 3], '\0'}; + token.push_back((uint8_t)strtol(hex, NULL, 16)); + i += 3; + } else { + token.push_back((uint8_t)t[i]); + } + } + if (!token.empty()) __dict_tokens.push_back(token); + } + AOUT("loaded %zu dict tokens\n", __dict_tokens.size()); +} + +// ③ 对求解改动的首个位置拼字典 token 产变体(cap 20),从种子出发 +static void save_dict_variants(const uint8_t *solved, size_t len) { + if (__dict_tokens.empty()) return; + size_t first = SIZE_MAX; + for (size_t i = 0; i < len && i < input_size; i++) + if (solved[i] != (uint8_t)input_buf[i]) { first = i; break; } + if (first == SIZE_MAX) return; + int variants = 0; + const int kMaxDictVariants = 20; + for (const auto &token : __dict_tokens) { + if (variants >= kMaxDictVariants) break; + if (first + token.size() > input_size) continue; + bool same_orig = true, same_solved = true; + for (size_t j = 0; j < token.size(); j++) { + if (token[j] != (uint8_t)input_buf[first + j]) same_orig = false; + if (first + j < len && token[j] != solved[first + j]) same_solved = false; + } + if (same_orig || same_solved) continue; + std::vector variant(input_buf, input_buf + input_size); + for (size_t j = 0; j < token.size(); j++) variant[first + j] = token[j]; + write_output(variant.data(), variant.size(), "dict"); + variants++; + } +} + +// 一个求出的新输入:写主输出 + hint + 字典变体 +static void emit_solution(const uint8_t *out, size_t len) { + std::string path = write_output(out, len, "sol"); + emit_hints(path, out, len); + save_dict_variants(out, len); +} + +// 事件:条件分支 → 解析成 tasks 入队(镜像 aflpp handle_cond) +static void handle_cond(pipe_msg &msg) { + if (msg.label == 0) return; + bool always_solve = (msg.flags & F_ADD_CONS) == 0; + bool loop_latch = (msg.flags & F_LOOP_LATCH) != 0; + bool loop_exit = (msg.flags & F_LOOP_EXIT) != 0; + + auto ctx = __cov_mgr->add_branch((void *)msg.addr, msg.id, msg.result != 0, + msg.context, loop_latch, loop_exit); + auto neg_ctx = std::make_shared(); + *neg_ctx = *ctx; + neg_ctx->direction = !ctx->direction; + + if (__cov_mgr->is_branch_interesting(neg_ctx) || always_solve) { + std::vector tasks; + if (__parser->parse_cond(msg.label, ctx->direction, msg.flags & F_ADD_CONS, tasks) != 0) + return; + for (auto const &tid : tasks) { + auto task = __parser->retrieve_task(tid); + if (task) __task_mgr->add_task(neg_ctx, task); + } + } +} + +// 事件:符号 GEP 索引 → 解析成 tasks 入队(镜像 aflpp handle_gep) +static void handle_gep(gep_msg &gmsg, pipe_msg &msg) { + if (msg.label == 0) return; + std::vector tasks; + if (__parser->parse_gep(gmsg.ptr_label, gmsg.ptr, gmsg.index_label, gmsg.index, + gmsg.num_elems, gmsg.elem_size, gmsg.current_offset, + false, tasks) != 0) + return; + auto ctx = std::make_shared(); + ctx->addr = (void *)msg.addr; + ctx->direction = true; + for (auto const &tid : tasks) { + auto task = __parser->retrieve_task(tid); + if (task) __task_mgr->add_task(ctx, task); + } +} + +// 事件循环后:抽干任务队列,每个 task 走求解级联,命中即写输出 +static void solve_all_tasks() { + size_t solved = 0; + for (auto task = __task_mgr->get_next_task(); task; task = __task_mgr->get_next_task()) { + for (auto &solver : __solvers) { + size_t out_size = 0; + auto r = solver->solve(task, (const uint8_t *)input_buf, input_size, + __out_buf, out_size); + if (r == rgd::SOLVER_SAT && out_size > 0) { + emit_solution(__out_buf, out_size); + solved++; + break; // 命中即止,进入下一个 task + } + } + } + AOUT("solved %zu tasks\n", solved); +} + +int main(int argc, char *const argv[]) { + if (argc != 3) { + fprintf(stderr, "Usage: %s target input\n", argv[0]); + exit(1); + } + char *program = argv[1]; + char *input = argv[2]; + + int is_stdin = 0; + int solve_ub = 0; + bool use_jigsaw = getenv("SYMSAN_USE_JIGSAW") != nullptr; // 可选 JIGSAW 梯度求解 + bool nested = getenv("SYMSAN_USE_NESTED") != nullptr; + + char *options = getenv("TAINT_OPTIONS"); + if (options) { + char *output = strstr(options, "output_dir="); + if (output) { + output += 11; + char *end = strchr(output, ':'); + if (end == NULL) end = strchr(output, ' '); + size_t n = end == NULL ? strlen(output) : (size_t)(end - output); + __output_dir = strndup(output, n); + } + char *taint_file = strstr(options, "taint_file="); + if (taint_file) { + taint_file += strlen("taint_file="); + char *end = strchr(taint_file, ':'); + if (end == NULL) end = strchr(taint_file, ' '); + size_t n = end == NULL ? strlen(taint_file) : (size_t)(end - taint_file); + if (n == 5 && !strncmp(taint_file, "stdin", 5)) is_stdin = 1; + } + // ④ 选择性符号化 focus_bytes(暂存,init 后下发) + char *focus = strstr(options, "focus_bytes="); + if (focus) { + focus += strlen("focus_bytes="); + char *end = strchr(focus, ':'); + if (end == NULL) end = strchr(focus, ' '); + size_t n = end == NULL ? strlen(focus) : (size_t)(end - focus); + if (n > 0) __focus_spec = strndup(focus, n); + } + char *debug_opt = strstr(options, "debug="); + if (debug_opt && (debug_opt[6] == '1')) __debug = 1; + if (strstr(options, "solve_ub=1")) solve_ub = 1; + } + + __emit_hints = getenv("SYMCC_EMIT_HINTS") != nullptr; // ② + load_dictionary(); // ③ + + // load input file (mmap,污点源) + struct stat st; + int input_fd = open(input, O_RDONLY); + if (input_fd == -1) { fprintf(stderr, "open input: %s\n", strerror(errno)); exit(1); } + fstat(input_fd, &st); + input_size = st.st_size; + input_buf = (char *)mmap(NULL, input_size, PROT_READ, MAP_PRIVATE, input_fd, 0); + if (input_buf == (void *)-1) { fprintf(stderr, "mmap input\n"); exit(1); } + + __out_buf = (uint8_t *)malloc(kMaxOut); + if (!__out_buf) { fprintf(stderr, "alloc out buf\n"); exit(1); } + + // setup launcher + 运行目标 + void *shm_base = symsan_init(program, uniontable_size); + if (shm_base == (void *)-1) { fprintf(stderr, "map shm\n"); exit(1); } + __label_info_base = (dfsan_label_info *)shm_base; // get_label_info 索引基址 + __max_label = uniontable_size / sizeof(dfsan_label_info); + if (symsan_set_input(is_stdin ? "stdin" : input) != 0) { fprintf(stderr, "set input\n"); exit(1); } + char *args[3] = {program, input, NULL}; + if (symsan_set_args(2, args) != 0) { fprintf(stderr, "set args\n"); exit(1); } + symsan_set_debug(__debug); + // "遇内存错误即退出"对 concolic 有害:目标合法的未初始化内存访问会在 bounds/GEP 追踪里 + // 产生 kInitializingLabel → 提前 Die() → 丢大量产出。默认关掉严格更优(见 fgtest.cpp 注)。 + // SYMSAN_MEMERR_EXIT=1 恢复;bounds 保留(GEP 求解),SYMSAN_NO_BOUNDS 可关。 + symsan_set_bounds_check(getenv("SYMSAN_NO_BOUNDS") ? 0 : 1); + symsan_set_exit_on_memerror(getenv("SYMSAN_MEMERR_EXIT") ? 1 : 0); + symsan_set_solve_ub(solve_ub); + symsan_set_focus_bytes(__focus_spec); // ④ init 之后下发 + + int ret = symsan_run(input_fd); + if (ret != 0) { fprintf(stderr, "launch target: %d\n", ret); exit(1); } + close(input_fd); + + // setup RGD 解析器 + 覆盖管理 + 任务队列 + 求解级联 + __parser = new rgd::RGDAstParser(shm_base, uniontable_size, nested, 200); + __cov_mgr = new rgd::EdgeCovManager(); + __task_mgr = new rgd::FIFOTaskManager(); + __solvers.emplace_back(std::make_shared()); // input-to-state 先行 + if (use_jigsaw) + __solvers.emplace_back(std::make_shared()); // JIGSAW(可选) + __solvers.emplace_back(std::make_shared()); // SMT 兜底(保证不弱于 Z3 基线) + + std::vector inputs; + inputs.push_back({(uint8_t *)input_buf, input_size}); + if (__parser->restart(inputs) != 0) { fprintf(stderr, "parser restart\n"); exit(1); } + + // 事件循环:构造求解任务 + pipe_msg msg; + gep_msg gmsg; + size_t msg_size; + memcmp_msg *mmsg = nullptr; + while (symsan_read_event(&msg, sizeof(msg), 0) > 0) { + switch (msg.msg_type) { + case cond_type: + handle_cond(msg); + break; + case gep_type: + if (symsan_read_event(&gmsg, sizeof(gmsg), 0) != sizeof(gmsg)) break; + if (msg.label != gmsg.index_label) break; + handle_gep(gmsg, msg); + break; + case memcmp_type: + if (!msg.flags) break; // 双符号操作数,无内容可读 + msg_size = sizeof(memcmp_msg) + msg.result; + mmsg = (memcmp_msg *)malloc(msg_size); + if (symsan_read_event(mmsg, msg_size, 0) != (ssize_t)msg_size) { free(mmsg); break; } + if (msg.label != mmsg->label) { free(mmsg); break; } + __parser->record_memcmp(msg.label, mmsg->content, msg.result); // 喂 I2S + free(mmsg); + break; + default: + break; + } + } + + // 抽干任务队列 → 求解 → 写输出 + solve_all_tasks(); + + symsan_destroy(); + exit(0); +} diff --git a/driver/launcher/launch.c b/driver/launcher/launch.c index b7e207d..c2d3080 100644 --- a/driver/launcher/launch.c +++ b/driver/launcher/launch.c @@ -50,6 +50,7 @@ struct symsan_config { int exit_on_memerror; int trace_file_size; int force_stdin; + char *focus_bytes; // 选择性符号化:仅污染这些输入偏移(如 "0-3"),NULL/空=全部 int dev_null_fd; @@ -86,6 +87,7 @@ void* symsan_init(const char *symsan_bin, const size_t uniontable_size) { g_config.exit_on_memerror = 1; g_config.trace_file_size = 0; g_config.force_stdin = 0; + g_config.focus_bytes = NULL; g_config.dev_null_fd = -1; g_config.exit_status = 0; g_config.is_killed = 0; @@ -218,6 +220,14 @@ int symsan_set_force_stdin(int enable) { return 0; } +__attribute__((visibility("default"))) +int symsan_set_focus_bytes(const char *spec) { + // 选择性符号化区间规格,如 "0-3" 或 "0-3,8-11";NULL/空串=符号化全部字节。 + free(g_config.focus_bytes); + g_config.focus_bytes = (spec && spec[0]) ? strdup(spec) : NULL; + return 0; +} + __attribute__((visibility("default"))) int symsan_run(int fd) { if (fd < 0) { @@ -256,14 +266,21 @@ int symsan_run(int fd) { return SYMSAN_NO_MEMORY; } + // 选择性符号化:仅当配置了 focus_bytes 才追加该段(空段避免设成空标志) + char focus_seg[512]; + focus_seg[0] = '\0'; + if (g_config.focus_bytes && g_config.focus_bytes[0]) { + snprintf(focus_seg, sizeof(focus_seg), ":focus_bytes=%s", g_config.focus_bytes); + } + // fds and configs could have been changed, so always set up new ones g_config.symsan_env = alloc_printf( "taint_file=\"%s\":shm_fd=%d:pipe_fd=%d:debug=%d:trace_bounds=%d:" - "solve_ub=%d:exit_on_memerror=%d:trace_fsize=%d:force_stdin=%d", + "solve_ub=%d:exit_on_memerror=%d:trace_fsize=%d:force_stdin=%d%s", g_config.input_file, g_config.shm_fd, g_config.pipefds[1], g_config.enable_debug, g_config.enable_bounds_check, g_config.enable_solve_ub, g_config.exit_on_memerror, - g_config.trace_file_size, g_config.force_stdin); + g_config.trace_file_size, g_config.force_stdin, focus_seg); if (g_config.symsan_env == NULL) { return SYMSAN_NO_MEMORY; } diff --git a/include/launch.h b/include/launch.h index 4249f22..7afd0c6 100644 --- a/include/launch.h +++ b/include/launch.h @@ -45,6 +45,10 @@ int symsan_set_trace_file_size(int enable); /// @brief set the force stdin mode for the target binary int symsan_set_force_stdin(int enable); +/// @brief 选择性符号化:仅污染指定输入偏移(如 "0-3" 或 "0-3,8-11"); +/// NULL 或空串表示符号化全部字节(默认)。移植自 SymCC 的 SYMCC_FOCUS_BYTES。 +int symsan_set_focus_bytes(const char *spec); + /// @brief run the target binary with the input file descriptor /// @param fd: input file descriptor, only used if input is "stdin" /// @return < 0 on syscall error, > 0 on setup error, 0 on success diff --git a/runtime/dfsan/dfsan_custom.cpp b/runtime/dfsan/dfsan_custom.cpp index 1ed98b5..7425e0b 100644 --- a/runtime/dfsan/dfsan_custom.cpp +++ b/runtime/dfsan/dfsan_custom.cpp @@ -229,12 +229,67 @@ static inline dfsan_label get_str_label(const char *s, dfsan_label s_label) { return get_str_label_n(s, s_label, len + 1, term_label); } +// 选择性符号化(移植自 SymCC 的 initFocusBytes / _sym_get_input_byte): +// 仅对 focus_bytes 指定的输入偏移打标签,其余偏移保持具体值(label 0 → 不污染)。 +// SymSan 的 taint 源是字节级的(每个输入偏移一个 label),因此在此单一枢纽处按偏移 +// 门控,即可让所有 read/pread/fread/mmap 等读入路径统一遵守选择集,缩小符号状态、 +// 减少无关字节的求解开销。区间语义与 SymCC 的 SYMCC_FOCUS_BYTES="s-e" 一致: +// 区间内符号化、区间外具体化。 +#define DFSAN_MAX_FOCUS_RANGES 256 +struct FocusRange { + off_t lo; // 闭区间下界 + off_t hi; // 闭区间上界 +}; +static FocusRange g_focus_ranges[DFSAN_MAX_FOCUS_RANGES]; +static int g_focus_count = -1; // -1=未初始化; 0=无 focus(全字节符号化); >0=区间数 + +// 解析 focus_bytes 规格串,如 "0-3" 或 "0-3,8-11" 或 "5"。不依赖 libc(strtol +// 在 DFSan 运行时里被拦截可能不安全),手工按十进制逐位累加。 +static void init_focus_bytes() { + g_focus_count = 0; + const char *p = flags().focus_bytes; + if (!p || !p[0]) return; // 未配置 → 保持默认(全字节符号化) + while (*p && g_focus_count < DFSAN_MAX_FOCUS_RANGES) { + while (*p == ',' || *p == ' ') ++p; // 跳过分隔符 + if (*p < '0' || *p > '9') { if (*p) ++p; continue; } + off_t lo = 0; + while (*p >= '0' && *p <= '9') { lo = lo * 10 + (*p - '0'); ++p; } + off_t hi = lo; + if (*p == '-') { + ++p; + hi = 0; + while (*p >= '0' && *p <= '9') { hi = hi * 10 + (*p - '0'); ++p; } + } + if (hi >= lo) { + g_focus_ranges[g_focus_count].lo = lo; + g_focus_ranges[g_focus_count].hi = hi; + ++g_focus_count; + } + } +} + +// 该输入偏移是否应被符号化。无 focus 配置时恒为 true(默认行为不变)。 +static inline bool focus_active(off_t offset) { + if (g_focus_count < 0) init_focus_bytes(); + if (g_focus_count == 0) return true; // 无 focus → 所有字节符号化 + for (int i = 0; i < g_focus_count; ++i) + if (offset >= g_focus_ranges[i].lo && offset <= g_focus_ranges[i].hi) + return true; + return false; +} + static inline dfsan_label get_label_for(int fd, off_t offset) { // check if fd is stdin, if so, the label hasn't been pre-allocated - if (is_stdin_taint() || (fd ==0 && flags().force_stdin)) - return dfsan_create_label((uint64_t)fd, (uint64_t)(current_stdin_offset++), 1); + if (is_stdin_taint() || (fd ==0 && flags().force_stdin)) { + off_t off = current_stdin_offset++; // 偏移必须始终自增以保持一致 + if (!focus_active(off)) return 0; // 选择性符号化:非 focus 字节保持具体 + return dfsan_create_label((uint64_t)fd, (uint64_t)off, 1); + } // if fd is a tainted file, the label should have been pre-allocated - else return (offset + CONST_OFFSET); + else { + if (!focus_active(offset)) return 0; // 选择性符号化:非 focus 字节保持具体 + return (offset + CONST_OFFSET); + } } static void *dfsan_memcpy(void *dest, const void *src, size_t n) { @@ -3938,8 +3993,11 @@ void __dfsw___libc_free(void *ptr, dfsan_label ptr_label) { static dfsan_label taint_getc(int fd, off_t offset, int ret) { if (ret != EOF && taint_get_file(fd)) { - dfsan_label label = label = dfsan_union(get_label_for(fd, offset), CONST_LABEL, ZExt, 32, 0, 0); + // BUG FIX: 原实现算出 label 却 `return 0`,导致每次 fgetc/getc 读入的字符都【丢污点】 + // → getc 逐字符读入的程序(coreutils uniq/md5sum/who 等)的输入永不符号化。改为返回 label。 + dfsan_label label = dfsan_union(get_label_for(fd, offset), CONST_LABEL, ZExt, 32, 0, 0); AOUT("%d label is readed by fgetc\n", label); + return label; } return 0; } diff --git a/runtime/dfsan/dfsan_flags.inc b/runtime/dfsan/dfsan_flags.inc index 046e3f3..cb4148f 100644 --- a/runtime/dfsan/dfsan_flags.inc +++ b/runtime/dfsan/dfsan_flags.inc @@ -54,3 +54,7 @@ DFSAN_FLAG(int, session_id, 0, "session/round id.") DFSAN_FLAG(bool, force_stdin, false, "force tainting stdin.") DFSAN_FLAG(bool, enum_gep, false, "enable GEP index enumeration.") DFSAN_FLAG(int, string_map_capacity, 256, "initial capacity for string label maps.") +DFSAN_FLAG(const char *, focus_bytes, "", + "Selective symbolization: only taint these input byte offsets, e.g. " + "\"0-3\" or \"0-3,8-11\". Empty = taint all bytes (default). Ported " + "from SymCC SYMCC_FOCUS_BYTES.")