diff --git a/.dockerignore b/.dockerignore index 94e999f9d8a..aa1f2ae026d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ Dockerfile .*sw* .dockerignore .github +.git/FETCH_HEAD \ No newline at end of file diff --git a/.github/workflows/parallel_tests.yml b/.github/workflows/parallel_tests.yml index 42426d233da..191bccc5c75 100644 --- a/.github/workflows/parallel_tests.yml +++ b/.github/workflows/parallel_tests.yml @@ -21,16 +21,27 @@ jobs: image: ubuntu:20.04 steps: - name: Update +<<<<<<< HEAD run: apt-get update -y - name: Install ssl run: apt-get install -y libssl-dev +======= + run: apt-get -qq update -y + - name: Install ssl + run: apt-get -qq install -y libssl-dev +>>>>>>> dev - name: Set up Python uses: actions/setup-python@v2 with: python-version: 3.9 - name: Install Python dev headers +<<<<<<< HEAD run: apt-get install -y libpython3.9-dev - uses: actions/checkout@v2 # Clones to $GITHUB_WORKSPACE. NOTE: this requires git > 2.18 (not on ubuntu 18.04 by default) to get .git directory +======= + run: apt-get -qq install -y libpython3.9-dev + - uses: actions/checkout@v4 # Clones to $GITHUB_WORKSPACE. NOTE: this requires git > 2.18 (not on ubuntu 18.04 by default) to get .git directory +>>>>>>> dev - name: Lint PyPANDA with flake8 run: | pip install --upgrade pip @@ -46,8 +57,13 @@ jobs: runs-on: panda-arc steps: - name: Install git +<<<<<<< HEAD run: sudo apt-get update -y && sudo apt-get install git -y - uses: actions/checkout@v2 # Clones to $GITHUB_WORKSPACE. NOTE: this requires git > 2.18 (not on ubuntu 18.04 by default) to get .git directory +======= + run: sudo apt-get -qq update -y && sudo apt-get -qq install git -y + - uses: actions/checkout@v4 # Clones to $GITHUB_WORKSPACE. NOTE: this requires git > 2.18 (not on ubuntu 18.04 by default) to get .git directory +>>>>>>> dev with: fetch-depth: 0 @@ -57,6 +73,7 @@ jobs: uses: docker/build-push-action@v5 with: context: ${{ github.workspace }} +<<<<<<< HEAD tags: panda_local:${{ github.sha }} target: developer - name: Minimal test of built container # Just test to see if one of our binaries is built @@ -70,14 +87,38 @@ jobs: path: panda_local.tar.gz retention-days: 2 taint_tests: +======= + tags: ghcr.io/${{ github.repository_owner }}/panda_local:${{ github.sha }} + target: developer + - name: Minimal test of built container # Just test to see if one of our binaries is built + run: docker run --rm "ghcr.io/${{ github.repository_owner }}/panda_local:${{ github.sha }}" /bin/bash -c 'exit $(/panda/build/arm-softmmu/panda-system-arm -help | grep -q "usage. panda-system-arm")' + - name: 'Login to Github Container Registry' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Push to ghcr.io + run: docker push ghcr.io/${{ github.repository_owner }}/panda_local:${{ github.sha }} + + tests: +>>>>>>> dev if: github.repository == 'panda-re/panda' runs-on: panda-arc needs: [build_container] strategy: matrix: - target: [i386, x86_64] - + include: + - test_type: "taint" + target: "i386" + - test_type: "taint" + target: "x86_64" + - test_type: "pypanda" + test_script: "all" + - test_type: "make_check" + test_script: "all" + steps: - name: Download a panda artifact uses: actions/download-artifact@v3 @@ -87,26 +128,43 @@ jobs: run: docker load -i panda_local.tar.gz # Given a container with PANDA installed at /panda, run the taint tests - name: Update +<<<<<<< HEAD run: sudo apt-get update -y - name: Install ssl run: sudo apt-get install -y wget - name: Run taint tests inside current container run: >- wget -q https://panda-re.mit.edu/qcows/linux/debian/7.3/x86/debian_7.3_x86.qcow -o wheezy_panda2.qcow2; +======= + run: sudo apt-get -qq update -y + - name: Install ssl + run: sudo apt-get -qq install -y wget + - name: Run Taint Tests + if: matrix.test_type == 'taint' + run: >- + wget -q -O wheezy_panda2.qcow2 https://panda-re.mit.edu/qcows/linux/debian/7.3/x86/debian_7.3_x86.qcow; +>>>>>>> dev wget -q https://panda-re.mit.edu/qcows/linux/ubuntu/1804/x86_64/bionic-server-cloudimg-amd64-noaslr-nokaslr.qcow2; docker run --name panda_test_${{ matrix.target }}_${GITHUB_RUN_ID} --mount type=bind,source=$(pwd)/wheezy_panda2.qcow2,target=/home/panda/regdir/qcows/wheezy_panda2.qcow2 --mount type=bind,source=$(pwd)/bionic-server-cloudimg-amd64-noaslr-nokaslr.qcow2,target=/home/panda/regdir/qcows/bionic-server-cloudimg-amd64-noaslr-nokaslr.qcow2 +<<<<<<< HEAD --rm -t "panda_local:${{ github.sha }}" bash -c +======= + --rm -t "ghcr.io/${{ github.repository_owner }}/panda_local:${{ github.sha }}" bash -c +>>>>>>> dev "cd /tmp; git clone https://github.com/panda-re/panda_test; cd ./panda_test/tests/taint2; + echo 'Running Record:'; python3 taint2_multi_arch_record_or_replay.py --arch ${{ matrix.target }} --mode record; + echo 'Running Replay:'; python3 taint2_multi_arch_record_or_replay.py --arch ${{ matrix.target }} --mode replay; sed -i '/^\s*$/d' taint2_log; if cat taint2_log; then echo 'Taint unit test log found!'; else echo 'Taint unit test log NOT found!' && exit 1; fi; echo -e '\nFailures:'; if grep 'fail' taint2_log; then echo 'TEST FAILED!' && exit 1; else echo -e 'None.\nTEST PASSED!' && exit 0; fi" +<<<<<<< HEAD sym_trace_tests: if: github.repository == 'panda-re/panda' runs-on: panda-arc @@ -119,8 +177,20 @@ jobs: steps: # Given a container with PANDA installed at /panda, run the taint tests - name: Run symbolic tracing tests inside current container +======= + - name: Run PyPanda Tests + if: matrix.test_type == 'pypanda' +>>>>>>> dev run: >- + wget -q https://panda-re.mit.edu/qcows/linux/ubuntu/1604/x86/ubuntu_1604_x86.qcow; + docker run --name panda_test_${{ matrix.test_script }}_${GITHUB_RUN_ID} + --mount type=bind,source=$(pwd)/ubuntu_1604_x86.qcow,target=/root/.panda/ubuntu_1604_x86.qcow + -e PANDA_TEST=yes --cap-add SYS_NICE + --rm -t "ghcr.io/${{ github.repository_owner }}/panda_local:${{ github.sha }}" bash -c + "cd /panda/panda/python/tests/ && make && pip3 install -r requirements.txt && chmod +x ./run_all_tests.sh && ./run_all_tests.sh"; + docker run --name panda_sym_test_${{ matrix.target }}_${GITHUB_RUN_ID} +<<<<<<< HEAD --rm -t "panda_local:${{ github.sha }}" bash -c "pip3 install capstone keystone-engine z3-solver; python3 /panda/panda/python/examples/unicorn/taint_sym_x86_64.py; if [ $? -eq 0 ]; then echo -e 'TEST PASSED!' && exit 0; else echo 'TEST FAILED!' && exit 1; fi" @@ -158,9 +228,18 @@ jobs: steps: - name: Run individual pypanda tests # TODO: pip requirements install here should be moved to Docker image build to save test time +======= + --rm -t "ghcr.io/${{ github.repository_owner }}/panda_local:${{ github.sha }}" bash -c + "pip3 install capstone keystone-engine z3-solver; python3 /panda/panda/python/examples/unicorn/taint_sym_x86_64.py; + if [ $? -eq 0 ]; then echo -e 'TEST PASSED!' && exit 0; else echo 'TEST FAILED!' && exit 1; fi" + + - name: Run make Tests + if: matrix.test_type == 'make_check' +>>>>>>> dev run: >- wget https://panda-re.mit.edu/qcows/linux/ubuntu/1604/x86/ubuntu_1604_x86.qcow; docker run --name panda_test_${{ matrix.test_script }}_${GITHUB_RUN_ID} +<<<<<<< HEAD --mount type=bind,source=$(pwd)/ubuntu_1604_x86.qcow,target=/root/.panda/ubuntu_1604_x86.qcow -e PANDA_TEST=yes --cap-add SYS_NICE --rm -t "panda_local:${{ github.sha }}" bash -c @@ -169,6 +248,15 @@ jobs: cleanup: # Cleanup after prior jobs finish - even if they fail needs: [taint_tests, sym_trace_tests, make_check, pypanda_tests] +======= + -e PANDA_TEST=yes --cap-add SYS_NICE + --rm -t "ghcr.io/${{ github.repository_owner }}/panda_local:${{ github.sha }}" bash -c + "cd /panda/build && make check" + + cleanup: + # Cleanup after prior jobs finish - even if they fail + needs: [tests] +>>>>>>> dev runs-on: panda-arc if: always() diff --git a/Dockerfile b/Dockerfile index 43a6d02fbf1..b933350fa05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,4 @@ ARG BASE_IMAGE="ubuntu:20.04" -# Note PANDA supports ubuntu:22.04, but docker versions <= 20.10.7 can't run 22.04 containers - ARG TARGET_LIST="x86_64-softmmu,i386-softmmu,arm-softmmu,aarch64-softmmu,ppc-softmmu,mips-softmmu,mipsel-softmmu,mips64-softmmu" ### BASE IMAGE @@ -63,7 +61,8 @@ RUN git -C /panda submodule update --init dtc && \ --target-list="${TARGET_LIST}" \ --prefix=/usr/local \ --disable-numa \ - --enable-llvm + --enable-llvm && \ + rm -rf /panda/.git RUN make -C /panda/build -j "$(nproc)" @@ -97,13 +96,29 @@ RUN python3 -m pip install --ignore-install pycparser && python3 -m pip install RUN ls -alt $(pip show pandare | grep Location: | awk '{print $2}')/pandare/autogen/ RUN bash -c "ls $(pip show pandare | grep Location: | awk '{print $2}')/pandare/autogen/panda_{aarch64_64,arm_32,mips64_64,mips_32,mipsel_32,ppc_32,ppc_64,x86_64_64,i386_32}.py" +# this layer is used to strip shared objects and change python data to be +# symlinks to the installed panda data directory +FROM installer as cleanup +RUN find /usr/local/lib/panda -name "*.so" -exec strip {} \; +RUN PKG=`pip show pandare | grep Location: | awk '{print $2}'`/pandare/data; \ + rm -rf $PKG/pc-bios && ln -s /usr/local/share/panda $PKG/pc-bios; \ + for arch in `find $PKG -name "*-softmmu" -type d -exec basename {} \;` ; do \ + ARCHP=$PKG/$arch; \ + SARCH=`echo $arch | cut -d'-' -f 1`; \ + rm $ARCHP/libpanda-$SARCH.so $ARCHP/llvm-helpers-$SARCH.bc; \ + ln -s /usr/local/share/panda/llvm-helpers-$SARCH.bc $ARCHP/llvm-helpers-$SARCH.bc1; \ + ln -s /usr/local/bin/libpanda-$SARCH.so $ARCHP/libpanda-$SARCH.so; \ + rm -rf $ARCHP/panda/plugins; \ + ln -s /usr/local/lib/panda/$SARCH/ $ARCHP/panda/plugins; \ + done + ### Copy files for panda+pypanda from installer - Stage 5 FROM base as panda # Copy panda + libcapstone.so* + libosi libraries -COPY --from=installer /usr/local /usr/local -COPY --from=installer /usr/lib/libcapstone* /usr/lib/ -COPY --from=installer /lib/libosi.so /lib/libiohal.so /lib/liboffset.so /lib/ +COPY --from=cleanup /usr/local /usr/local +COPY --from=cleanup /usr/lib/libcapstone* /usr/lib/ +COPY --from=cleanup /lib/libosi.so /lib/libiohal.so /lib/liboffset.so /lib/ # Workaround issue #901 - ensure LD_LIBRARY_PATH contains the panda plugins directories #ARG TARGET_LIST="x86_64-softmmu,i386-softmmu,arm-softmmu,ppc-softmmu,mips-softmmu,mipsel-softmmu" diff --git a/panda/include/panda/callbacks/cb-defs.h b/panda/include/panda/callbacks/cb-defs.h index 6170f6d699f..39f94216cc0 100644 --- a/panda/include/panda/callbacks/cb-defs.h +++ b/panda/include/panda/callbacks/cb-defs.h @@ -57,6 +57,7 @@ typedef enum panda_cb_type { PANDA_CB_HD_WRITE, // Each HDD write PANDA_CB_GUEST_HYPERCALL, // Hypercall from the guest (e.g. CPUID) PANDA_CB_MONITOR, // Monitor callback + PANDA_CB_QMP, // QMP callback PANDA_CB_CPU_RESTORE_STATE, // In cpu_restore_state() (fault/exception) PANDA_CB_BEFORE_LOADVM, // at start of replay, before loadvm PANDA_CB_ASID_CHANGED, // When CPU asid (address space identifier) changes @@ -592,6 +593,23 @@ typedef union panda_cb { */ int (*monitor)(Monitor *mon, const char *cmd); + /* Callback ID: PANDA_CB_QMP + + qmp: + Called when someone sends an unhandled QMP command + + Arguments: + char *command: the command string as json + char *args: the arguments string as json + char **result: pointer to a json result or NULL + + Helper call location: TBA + + Return value: + bool: true IFF the command was handled by the plugin + */ + bool (*qmp)(char *command, char* args, char **result); + /* Callback ID: PANDA_CB_CPU_RESTORE_STATE @@ -1544,6 +1562,23 @@ typedef union panda_cb_with_context { */ int (*monitor)(void* context, Monitor *mon, const char *cmd); + /* Callback ID: PANDA_CB_QMP + + qmp: + Called when someone sends an unhandled QMP command + + Arguments: + char *command: the command string as json + char *args: the arguments string as json + char **result: pointer to a json result or NULL + + Helper call location: TBA + + Return value: + bool: true IFF the command was handled by the plugin + */ + bool (*qmp)(void* context, char *command, char* args, char **result); + /* Callback ID: PANDA_CB_CPU_RESTORE_STATE diff --git a/panda/include/panda/callbacks/cb-support.h b/panda/include/panda/callbacks/cb-support.h index af894b7f4d5..b7009d5b971 100644 --- a/panda/include/panda/callbacks/cb-support.h +++ b/panda/include/panda/callbacks/cb-support.h @@ -75,6 +75,7 @@ bool panda_callbacks_after_find_fast(CPUState *cpu, TranslationBlock *tb, bool b int panda_callbacks_insn_exec(CPUState *env, target_ptr_t pc); int panda_callbacks_after_insn_exec(CPUState *env, target_ptr_t pc); int panda_callbacks_monitor(Monitor *mon, const char *cmd); +bool panda_callbacks_qmp(char *command, char *args, char **result); int panda_callbacks_before_loadvm(void); void panda_callbacks_replay_hd_transfer(CPUState *env, uint32_t type, target_ptr_t src_addr, target_ptr_t dest_addr, size_t num_bytes); void panda_callbacks_after_machine_init(CPUState *env); diff --git a/panda/include/panda/callbacks/cb-trampolines.h b/panda/include/panda/callbacks/cb-trampolines.h index f13ca5be3a3..2fbae221422 100644 --- a/panda/include/panda/callbacks/cb-trampolines.h +++ b/panda/include/panda/callbacks/cb-trampolines.h @@ -19,6 +19,7 @@ void panda_cb_trampoline_phys_mem_after_write(void* context, CPUState *env, targ int panda_cb_trampoline_insn_exec(void* context, CPUState *env, target_ptr_t pc); int panda_cb_trampoline_after_insn_exec(void* context, CPUState *env, target_ptr_t pc); int panda_cb_trampoline_monitor(void* context, Monitor *mon, const char *cmd); +bool panda_cb_trampoline_qmp(void* context, char *command, char *args, char **result); //int panda_cb_trampoline_before_loadvm(void* context); void panda_cb_trampoline_replay_hd_transfer(void* context, CPUState *env, uint32_t type, target_ptr_t src_addr, target_ptr_t dest_addr, size_t num_bytes); void panda_cb_trampoline_after_machine_init(void* context, CPUState *env); diff --git a/panda/include/panda/plugin.h b/panda/include/panda/plugin.h index 671533f71a0..77f2993b9d6 100644 --- a/panda/include/panda/plugin.h +++ b/panda/include/panda/plugin.h @@ -67,8 +67,10 @@ void panda_disable_plugin(void *plugin); // Structure to store metadata about a plugin typedef struct panda_plugin { - char name[256]; // Currently basename(filename) - void *plugin; // Handle to the plugin (for use with dlsym()) + char *name; // Plugin name: basename(filename) + void *plugin; // Handle to the plugin (for use with dlsym()) + bool unload; // When true, unload plugin when safe + bool exported_symbols; // True if plugin dlopened with RTLD_GLOBAL } panda_plugin; @@ -181,10 +183,6 @@ void panda_unregister_callbacks(void *plugin); */ bool panda_load_plugin(const char *filename, const char *plugin_name); - -bool _panda_load_plugin(const char *filename, const char *plugin_name, bool library_mode); - - /** * panda_add_arg() - Add an argument to those for a plugin. * @plugin_name: The name of the plugin. @@ -194,10 +192,6 @@ bool _panda_load_plugin(const char *filename, const char *plugin_name, bool libr */ bool panda_add_arg(const char *plugin_name, const char *plugin_arg); - -// I think this is not used anywhere? -bool panda_load_external_plugin(const char *filename, const char *plugin_name, void *plugin_uuid, void *init_fn_ptr); - /** * panda_get_plugin_by_name() - Returns pointer to the plugin of this name. * @name: The name of the desired plugin. @@ -232,8 +226,6 @@ void panda_unload_plugins(void); extern bool panda_update_pc; extern bool panda_use_memcb; extern panda_cb_list *panda_cbs[PANDA_CB_LAST]; -extern bool panda_plugins_to_unload[MAX_PANDA_PLUGINS]; -extern bool panda_plugin_to_unload; extern bool panda_tb_chaining; // this stuff is used by the new qemu cmd-line arg '-os os_name' diff --git a/panda/plugins/callwitharg/Makefile b/panda/plugins/callwitharg/Makefile new file mode 100644 index 00000000000..659a4215597 --- /dev/null +++ b/panda/plugins/callwitharg/Makefile @@ -0,0 +1,3 @@ +CFLAGS+= -std=c++17 +$(PLUGIN_TARGET_DIR)/panda_$(PLUGIN_NAME).so: \ + $(PLUGIN_OBJ_DIR)/$(PLUGIN_NAME).o diff --git a/panda/plugins/callwitharg/README.md b/panda/plugins/callwitharg/README.md new file mode 100644 index 00000000000..c827ddb2d44 --- /dev/null +++ b/panda/plugins/callwitharg/README.md @@ -0,0 +1,45 @@ +Plugin: CallWithArg +=========== + +Summary +------- + +At every function call, check the first N (potential) arguments to see if any are a specified integer value or a pointer to a specified string value. If so, trigger a custom PPP event. + +The goal here is to dynamically identify functions that take a known value as an argument. **Many false positives are to be expected!** But if you test multiple values, you can use this plugin to easily identify a hookable function in a target program to build custom introspection gadgets. + +Arguments +--------- + +`targets`: an `_` seperated list of hex numbers and/or strings to check for (e.g., `0x1234_hello world_0xABCDEF`) +`verbose`: If set to 1, print on every detected call. +`N`: How many arguments to examine. Default 2. Only supports standard linux calling conventions for now. + + +Dependencies +------------ +`callstack_instr` + +APIs and Callbacks +------------------ +API +* `void add_target_string(char* s)`, `void add_target_num(target_ulong x)`: Add a new target +* `bool remove_target_string(char* s)`, `bool remove_target_num(target_ulong x)`: Remove an existing target. Returns true if the argument was previously a target + +Callbacks: +``` +on_call_match_num(CPUState *cpu, target_ulong* args, uint matching_idx, uint args_read); +``` + +``` +on_call_match_str(CPUState *cpu, target_ulong* args, uint matching_idx, char* value, uint args_read); +``` + +Example +------- +``` +$(python3 -m pandare.qcows x86_64) -panda callwitharg:targets=root_hello_0x41414141,verbose=1 +root@guest# echo hello +root@guest# echo AAAA +root@guest# whoami +``` \ No newline at end of file diff --git a/panda/plugins/callwitharg/callwitharg.cpp b/panda/plugins/callwitharg/callwitharg.cpp new file mode 100644 index 00000000000..aa895df39f0 --- /dev/null +++ b/panda/plugins/callwitharg/callwitharg.cpp @@ -0,0 +1,191 @@ +#include "panda/plugin.h" +#include "panda/plugin_plugin.h" +#include +#include +#include + +#include "callwitharg.h" +#include "callstack_instr/callstack_instr.h" + +extern "C" { +#include "callwitharg_int_fns.h" + bool init_plugin(void *); + void uninit_plugin(void *); + PPP_PROT_REG_CB(on_call_match_str); + PPP_PROT_REG_CB(on_call_match_num); +} + +PPP_CB_BOILERPLATE(on_call_match_str); +PPP_CB_BOILERPLATE(on_call_match_num); + +uint N; +bool verbose; +void on_call_with_args(CPUState *cpu, target_ulong func_pc); + +// Sets of the targets we're looking for +std::unordered_set string_targets; +std::unordered_set int_targets; +void add_target_string(char* target) { + if (verbose) { + printf("Adding string target %s\n", target); + } + std::string target_s = std::string(target); + string_targets.insert(target_s); +} + +bool remove_target_string(char* target) { + if (verbose) { + printf("Removing string target %s\n", target); + } + std::string target_s = std::string(target); + return string_targets.erase(target_s) > 0; +} + +void add_target_num(target_ulong target) { + if (verbose) { + printf("Adding number target " TARGET_FMT_lx "\n", target); + } + int_targets.insert(target); +} + +bool remove_target_num(target_ulong target) { + if (verbose) { + printf("Removing number target " TARGET_FMT_lx "\n", target); + } + return int_targets.erase(target) > 0; +} + +bool init_plugin(void *self) { + std::unique_ptr args( + panda_get_args("callwitharg"), panda_free_args); + + const char * target_str_const = panda_parse_string_opt(args.get(), "targets", "", "Hex values and strings to search for in arguments. Seperated by _s"); + + if (strlen(target_str_const) > 0) { + char *target_str = strdup(target_str_const); // Make a mutable copy + + // First split string on _'s, then iterate over each target and + // add to the appropriate set + char* target = strtok(target_str, "_"); + while (target != NULL) { + // If target is a hex value, add it to the int_targets set + if (target[0] == '0' && target[1] == 'x') { + target_ulong target_num = strtoul(target, NULL, 16); + add_target_num(target_num); + } else { + // Otherwise, add it to the string_targets set + add_target_string(target); + } + target = strtok(NULL, "_"); + } + } + + verbose = panda_parse_bool_opt(args.get(), "verbose", "enable verbose output"); + N = (uint)panda_parse_uint32_opt(args.get(), "N", 2, "Maximum number of arguments to examine on each call"); + +#if defined(TARGET_ARM) || defined(TARGET_MIPS) || defined(TARGET_X86_64) + PPP_REG_CB("callstack_instr", on_call, on_call_with_args); + return true; +#else + printf("ERROR: Unsupported architecture for targetcmp\n"); + return false; +#endif +} + +typedef struct { + target_ulong *args; + size_t count; +} Arguments; + +bool _get_args_for_arch(CPUArchState *env, Arguments *args, int N) { +#ifdef TARGET_ARM + for (int i = 0; i < N; ++i) { + args->args[i] = env->regs[i]; + } +#elif defined(TARGET_MIPS) + for (int i = 0; i < N; ++i) { + args->args[i] = env->active_tc.gpr[4 + i]; + } +#elif defined(TARGET_X86_64) + // Handle SysV ABI here, or make it a parameter to the function + const int regs[] = {7, 6}; // RDI, RSI + for (int i = 0; i < N; ++i) { + args->args[i] = env->regs[regs[i]]; + } +#else + return false; // Error +#endif + + return true; // All good +} + +bool get_n_args(CPUState *cpu, Arguments *args, uint n) { + CPUArchState *UNUSED(env) = (CPUArchState *)cpu->env_ptr; + args->args = (target_ulong*)malloc(n * sizeof(target_ulong)); + args->count = N; + return _get_args_for_arch(env, args, N); +} + + +// Called on every guest function call +void on_call_with_args(CPUState *cpu, target_ulong func_pc) { + + // What kind of target do we have? + bool have_strings = string_targets.size() > 0; + bool have_nums = int_targets.size() > 0; + + size_t max_len = 0; + + if (!have_strings && !have_nums) return; + + if (have_strings) { + for (auto it = string_targets.begin(); it != string_targets.end(); ++it) { + if (it->length() > max_len) { + max_len = it->length(); + } + } + } + + Arguments args; + if (!get_n_args(cpu, &args, N)) { + // Error! + printf("Failed to get %d args\n", N); + return; + } + + // Check the up to N arguments for matches + for (uint i=0; i < N; i++) { + // Is argument a string or a number? + if (have_nums) { + if (int_targets.find(args.args[i]) != int_targets.end()) { + if (verbose) { + printf("Found target " TARGET_FMT_lx " in call at " TARGET_FMT_lx ". In argument %d\n", args.args[i], func_pc, i); + } + PPP_RUN_CB(on_call_match_num, cpu, func_pc, args.args, i, N); + } + } + + if (have_strings) { + // Read string from guest memory + char str[max_len+1]; + if (panda_virtual_memory_read(cpu, args.args[i], (uint8_t*)str, max_len) != 0) { + // Read failed + continue; + } + str[max_len] = '\0'; + + // Check if string is in set of targets + std::string str_s = std::string(str); + if (string_targets.find(str_s) != string_targets.end()) { + if (verbose) { + printf("Found string target %s in call at " TARGET_FMT_lx ". In argument %d\n", str, func_pc, i); + } + PPP_RUN_CB(on_call_match_str, cpu, func_pc, args.args, str, i, N); + } + } + } + free(args.args); +} + +void uninit_plugin(void *self) { +} diff --git a/panda/plugins/callwitharg/callwitharg.h b/panda/plugins/callwitharg/callwitharg.h new file mode 100644 index 00000000000..b629f8772d2 --- /dev/null +++ b/panda/plugins/callwitharg/callwitharg.h @@ -0,0 +1,15 @@ +#ifndef __CALLWITHARG_H +#define __CALLWITHARG_H + +// BEGIN_PYPANDA_NEEDS_THIS -- do not delete this comment bc pypanda +// api autogen needs it. And don't put any compiler directives +// between this and END_PYPANDA_NEEDS_THIS except includes of other +// files in this directory that contain subsections like this one. + +PPP_CB_TYPEDEF(void, on_call_match_num, CPUState *env, target_ulong func_addr, target_ulong *args, uint matching_idx, uint args_read); +PPP_CB_TYPEDEF(void, on_call_match_str, CPUState *env, target_ulong func_addr, target_ulong *args, char* value, uint matching_idx, uint args_read); + +// END_PYPANDA_NEEDS_THIS -- do not delete this comment! + +#endif + diff --git a/panda/plugins/callwitharg/callwitharg_int.h b/panda/plugins/callwitharg/callwitharg_int.h new file mode 100644 index 00000000000..cd70a048c7a --- /dev/null +++ b/panda/plugins/callwitharg/callwitharg_int.h @@ -0,0 +1,3 @@ +typedef void target_ulong; +typedef void bool; +#include "callwitharg_int_fns.h" \ No newline at end of file diff --git a/panda/plugins/callwitharg/callwitharg_int_fns.h b/panda/plugins/callwitharg/callwitharg_int_fns.h new file mode 100644 index 00000000000..e2973885afc --- /dev/null +++ b/panda/plugins/callwitharg/callwitharg_int_fns.h @@ -0,0 +1,16 @@ +#ifndef __CALLWITHARG_INT_FNS_H__ +#define __CALLWITHARG_INT_FNS_H__ + +// BEGIN_PYPANDA_NEEDS_THIS -- do not delete this comment bc pypanda +// api autogen needs it. And don't put any compiler directives +// between this and END_PYPANDA_NEEDS_THIS except includes of other +// files in this directory that contain subsections like this one. + +// Public interface +void add_target_string(char* target); +bool remove_target_string(char* target); +void add_target_num(target_ulong target); +bool remove_target_num(target_ulong target); +// END_PYPANDA_NEEDS_THIS -- do not delete this comment! + +#endif diff --git a/panda/plugins/config.panda b/panda/plugins/config.panda index c30be02bb25..e2a3c5227e9 100644 --- a/panda/plugins/config.panda +++ b/panda/plugins/config.panda @@ -4,6 +4,7 @@ asid_instr_count asidstory callfunc callstack_instr +callwitharg checkpoint collect_code correlatetaps @@ -13,6 +14,7 @@ coverage dynamic_symbols edge_coverage filereadmon +findcall forcedexec gdb hooks @@ -47,6 +49,7 @@ snake_hook stringsearch syscalls2 syscalls_logger +targetcmp textprinter trace track_intexc diff --git a/panda/plugins/coverage/EdgeInstrumentationDelegate.cpp b/panda/plugins/coverage/EdgeInstrumentationDelegate.cpp index 0400878f93c..ee0c0634b50 100644 --- a/panda/plugins/coverage/EdgeInstrumentationDelegate.cpp +++ b/panda/plugins/coverage/EdgeInstrumentationDelegate.cpp @@ -39,7 +39,8 @@ static target_ulong fetch_register_value(size_t cpu_offset, target_ulong mask, target_ulong shr) { CPUArchState *env = static_cast(first_cpu->env_ptr); - return (*(reinterpret_cast((env + cpu_offset))) & mask) + // don't want to do pointer arithmetic, as cpu_offset in wrong units + return (*(reinterpret_cast((((size_t)env) + cpu_offset))) & mask) >> shr; } #define MK_REG_FETCHER(cpu_state_variable, mask, shift_right) \ @@ -49,7 +50,7 @@ static target_ulong fetch_register_value(size_t cpu_offset, target_ulong mask, static target_ulong fetch_segment_value(size_t cpu_offset) { CPUArchState *env = static_cast(first_cpu->env_ptr); - return reinterpret_cast((env + cpu_offset))->base; + return reinterpret_cast((((size_t)env) + cpu_offset))->base; } #define MK_SEG_FETCHER(cpu_state_variable) \ std::bind(fetch_segment_value, offsetof(CPUArchState, cpu_state_variable)) @@ -98,6 +99,10 @@ const static std::unordered_map { X86_REG_CS, MK_SEG_FETCHER(segs[R_CS]) }, + { X86_REG_DS, MK_SEG_FETCHER(segs[R_DS]) }, + + { X86_REG_FS, MK_SEG_FETCHER(segs[R_FS]) }, + { X86_REG_SS, MK_SEG_FETCHER(segs[R_SS]) }, { X86_REG_GS, MK_SEG_FETCHER(segs[R_GS]) }, @@ -128,7 +133,7 @@ const static std::unordered_map // control flow instructions have at most two targets, we can get away with // this struct and avoid the worst case O(N) complexity of a hash table and // this should be more cache friendly because setting the jump targets for -// the current thread basiclaly a memcpy or assignment. +// the current thread basically a memcpy or assignment. struct JumpTargets { bool has_dst1; @@ -146,7 +151,7 @@ struct EdgeState // Flag for enabling or disabling coverage. bool cov_enabled; - // Maps Thread ID -> Previos Blocks + // Maps Thread ID -> Previous Blocks std::unordered_map prev_blocks; // Previous Block for Current Thread Block* prev_block; @@ -247,8 +252,12 @@ static void jmp_mem_direct_callback(EdgeState *edge_state, return; } target_ulong jump_target = 0x0; - panda_virtual_memory_read(cpu, direct_address, + int retcode = panda_virtual_memory_read(cpu, direct_address, reinterpret_cast(&jump_target), sizeof(jump_target)); + if (retcode != 0) { + printf("error reading panda memory for block 0x" TARGET_FMT_lx "\n", + prev_block_addr); + } update_jump_targets(edge_state, prev_block_addr, prev_block_size, { .has_dst1 = true, .dst1 = jump_target, @@ -269,8 +278,12 @@ static void jmp_mem_indexed_callback(EdgeState *edge_state, } target_ulong address = (*index_register_fetcher)() * scale + disp; target_ulong jump_target = 0x0; - panda_virtual_memory_read(cpu, address, + int retcode = panda_virtual_memory_read(cpu, address, reinterpret_cast(&jump_target), sizeof(jump_target)); + if (retcode != 0) { + printf("error reading panda memory for block 0x" TARGET_FMT_lx "\n", + prev_block_addr); + } update_jump_targets(edge_state, prev_block_addr, prev_block_size, { .has_dst1 = true, .dst1 = jump_target, @@ -290,8 +303,41 @@ static void jmp_mem_indirect(EdgeState* edge_state, CPUState *cpu, } target_ulong address = (*base_register_fetch)() + disp; target_ulong jump_target = 0x0; - panda_virtual_memory_read(cpu, address, + int retcode = panda_virtual_memory_read(cpu, address, + reinterpret_cast(&jump_target), sizeof(jump_target)); + if (retcode != 0) { + printf("error reading panda memory for block 0x" TARGET_FMT_lx "\n", + prev_block_addr); + } + update_jump_targets(edge_state, prev_block_addr, prev_block_size, { + .has_dst1 = true, + .dst1 = jump_target, + .has_dst2 = false, + .dst2 = 0x0 + }); +} + +static void jmp_mem_indirect_rip(EdgeState* edge_state, CPUState *cpu, + target_ulong prev_block_addr, + target_ulong prev_block_size, + int64_t disp) +{ + if (!edge_state->cov_enabled) { + return; + } + // this instrumentation is called just before the prev_block is executed + // so the RIP register has prev_block_addr in it, not the address of the + // next block in memory after this one; fortunately, we have enough info + // to calculate the address of the next block without using a register + // fetcher + target_ulong address = prev_block_addr + prev_block_size + disp; + target_ulong jump_target = 0x0; + int retcode = panda_virtual_memory_read(cpu, address, reinterpret_cast(&jump_target), sizeof(jump_target)); + if (retcode != 0) { + printf("error reading panda memory for block 0x" TARGET_FMT_lx "\n", + prev_block_addr); + } update_jump_targets(edge_state, prev_block_addr, prev_block_size, { .has_dst1 = true, .dst1 = jump_target, @@ -313,8 +359,12 @@ static void jmp_mem_indirect_no_index(EdgeState* edge_state, CPUState *cpu, target_ulong address = (*segment_register_fetch)() + (*base_register_fetch)() + disp; target_ulong jump_target = 0x0; - panda_virtual_memory_read(cpu, address, + int retcode = panda_virtual_memory_read(cpu, address, reinterpret_cast(&jump_target), sizeof(jump_target)); + if (retcode != 0) { + printf("error reading panda memory for block 0x" TARGET_FMT_lx "\n", + prev_block_addr); + } update_jump_targets(edge_state, prev_block_addr, prev_block_size, { .has_dst1 = true, .dst1 = jump_target, @@ -336,8 +386,12 @@ static void jmp_mem_indirect_disp_si_callback(EdgeState *edge_state, } target_ulong address = (*brf)() + disp + (*irf)() * scale; target_ulong jump_target = 0x0; - panda_virtual_memory_read(cpu, address, + int retcode = panda_virtual_memory_read(cpu, address, reinterpret_cast(&jump_target), sizeof(jump_target)); + if (retcode != 0) { + printf("error reading panda memory for block 0x" TARGET_FMT_lx "\n", + prev_block_addr); + } update_jump_targets(edge_state, prev_block_addr, prev_block_size, { .has_dst1 = true, .dst1 = jump_target, @@ -375,8 +429,12 @@ static void ret_callback(EdgeState *edge_state, // Read the return target address off the stack. CPUArchState *env_ptr = static_cast(cpu->env_ptr); target_ulong return_addr = 0x0; - panda_virtual_memory_read(cpu, env_ptr->regs[R_ESP], + int retcode = panda_virtual_memory_read(cpu, env_ptr->regs[R_ESP], reinterpret_cast(&return_addr), sizeof(return_addr)); + if (retcode != 0) { + printf("error reading panda memory for block 0x" TARGET_FMT_lx "\n", + prev_block_addr); + } update_jump_targets(edge_state, prev_block_addr, prev_block_size, { .has_dst1 = true, .dst1 = return_addr, @@ -414,6 +472,21 @@ static void instrument_jcc(EdgeState *edge_state, CPUState *cpu, TCGOp *op, insert_call(&op, &jcc_callback, edge_state, tb->pc, tb->size, nit, jt); } +static const RegisterFetcher *get_register_fetcher(cs_insn *insn, unsigned int reg) +{ + // print some useful debugging information before give up if can't get + // the requested register fetcher + const RegisterFetcher *rf = nullptr; + try { + rf = &CS_TO_QEMU_REG_FETCH.at(reg); + } catch (std::out_of_range& err) { + printf("Error getting fetcher for register %d at address 0x%lx\n", reg, + insn->address); + throw; + } + return rf; +} + static void instrument_jmp(EdgeState *edge_state, CPUState *cpu, TCGOp *op, TranslationBlock *tb, cs_insn *insn) { @@ -422,14 +495,13 @@ static void instrument_jmp(EdgeState *edge_state, CPUState *cpu, TCGOp *op, target_ulong jt = static_cast(jmp_op.imm); insert_call(&op, static_jmp_callback, edge_state, tb->pc, tb->size, jt); } else if (X86_OP_MEM == jmp_op.type) { - static const RegisterFetcher *INVALID_REGISTER = - &CS_TO_QEMU_REG_FETCH.at(X86_REG_INVALID); - const RegisterFetcher *srf = &CS_TO_QEMU_REG_FETCH.at( + get_register_fetcher(insn, X86_REG_INVALID); + const RegisterFetcher *srf = get_register_fetcher(insn, jmp_op.mem.segment); - const RegisterFetcher *brf = &CS_TO_QEMU_REG_FETCH.at( + const RegisterFetcher *brf = get_register_fetcher(insn, jmp_op.mem.base); - const RegisterFetcher *irf = &CS_TO_QEMU_REG_FETCH.at( + const RegisterFetcher *irf = get_register_fetcher(insn, jmp_op.mem.index); #ifdef EDGE_INST_DEBUG @@ -451,9 +523,15 @@ static void instrument_jmp(EdgeState *edge_state, CPUState *cpu, TCGOp *op, } else if (INVALID_REGISTER == srf && INVALID_REGISTER == irf) { - // indirect addressing - insert_call(&op, jmp_mem_indirect, edge_state, cpu, tb->pc, - tb->size, brf, jmp_op.mem.disp); + if (X86_REG_RIP == jmp_op.mem.base) { + // RIP relative addressing + insert_call(&op, jmp_mem_indirect_rip, edge_state, cpu, tb->pc, + tb->size, jmp_op.mem.disp); + } else { + // indirect addressing + insert_call(&op, jmp_mem_indirect, edge_state, cpu, tb->pc, + tb->size, brf, jmp_op.mem.disp); + } } else if (INVALID_REGISTER == srf && INVALID_REGISTER == brf) { @@ -491,7 +569,7 @@ static void instrument_jmp(EdgeState *edge_state, CPUState *cpu, TCGOp *op, } } else if (X86_OP_REG == jmp_op.type) { - const RegisterFetcher *reg_fetcher = &CS_TO_QEMU_REG_FETCH.at( + const RegisterFetcher *reg_fetcher = get_register_fetcher(insn, jmp_op.reg); insert_call(&op, jmp_reg_callback, edge_state, cpu, tb->pc, tb->size, reg_fetcher); diff --git a/panda/plugins/coverage/README.md b/panda/plugins/coverage/README.md index 89c02883dfb..ed0ee7d3589 100644 --- a/panda/plugins/coverage/README.md +++ b/panda/plugins/coverage/README.md @@ -16,7 +16,7 @@ execution is also stored, regardless of mode. (A value of 1 means the block was executed in kernel mode.) For the `osi-block` and `asid-block` modes, the mode used to create the CSV file is also written at the top of the file, to cue parsers in as to how to interpret the file. `edge` mode writes two blocks per -records where the first block represents the "from" node in a control flow +record where the first block represents the "from" node in a control flow graph (CFG) and the second represents the "to" node. Note that the `edge` mode requires OSI and only works with X86 guests. @@ -69,10 +69,10 @@ adjusted manually for DOS operating systems. Arguments --------- -* `filename` - The name of the file to output (default: `coverage.csv`). +* `filename` - The name of the file to output (default: `coverage.csv`). * `mode` - Output mode, one of `asid-block`, `osi-block`, or `edge` (default: `asid-block`). Note that `edge` requires OSI. -* `full` - When `true`, logs each record every time it is generated (default: +* `full` - When `true`, logs each record every time it is generated (default: `false`) * `summary` - When `true`, only report total distinct blocks executed per process. Requires OSI and a mode of `osi-block` (default: `false`) * `start_disabled` - When `true`, does not start data collection when the @@ -111,12 +111,14 @@ None Example ------- Use the coverage plugin with a recording: + ``` panda-system-i386 -m 2G -replay test \ -os windows-32-xpsp3 \ -panda coverage:filename=test_coverage.csv,mode=osi-block ``` Use the coverage plugin on a live system: + ``` panda-system-i386 -monitor stdio -m 2G -net nic -net user -os linux-32-.+ -panda osi -panda osi_linux:kconf_file=myconf.conf,kconf_group=mygroup -hda myimage.img (qemu) load_plugin coverage,filename=test01.csv,mode=osi-block diff --git a/panda/plugins/coverage/coverage.cpp b/panda/plugins/coverage/coverage.cpp index d19ceb02910..f6237d8db8d 100644 --- a/panda/plugins/coverage/coverage.cpp +++ b/panda/plugins/coverage/coverage.cpp @@ -42,8 +42,7 @@ using namespace coverage; const char *DEFAULT_FILE = "coverage.csv"; // commands that can be accessed through the QEMU monitor -const char *MONITOR_HELP = "help"; -constexpr size_t MONITOR_HELP_LEN = 4; +const std::string MONITOR_HELP = "help"; const std::string MONITOR_ENABLE = "coverage_enable"; const std::string MONITOR_DISABLE = "coverage_disable"; @@ -124,6 +123,11 @@ int monitor_callback(Monitor *mon, const char *cmd_cstr) std::cerr << "Error enabling instrumentation: " << err.code().message() << "\n"; } } + } else if (0 == cmd.find(MONITOR_HELP)) { + log_message("coverage_enable=filename: start logging coverage " + "information to the named file"); + log_message("coverage_disable: stop logging coverage information " + "and close the current file"); } return 0; } diff --git a/panda/plugins/findcall/Makefile b/panda/plugins/findcall/Makefile new file mode 100644 index 00000000000..659a4215597 --- /dev/null +++ b/panda/plugins/findcall/Makefile @@ -0,0 +1,3 @@ +CFLAGS+= -std=c++17 +$(PLUGIN_TARGET_DIR)/panda_$(PLUGIN_NAME).so: \ + $(PLUGIN_OBJ_DIR)/$(PLUGIN_NAME).o diff --git a/panda/plugins/findcall/README.md b/panda/plugins/findcall/README.md new file mode 100644 index 00000000000..cdef066cba6 --- /dev/null +++ b/panda/plugins/findcall/README.md @@ -0,0 +1,23 @@ +Plugin: FindCall +=========== + +Summary +------- + +At every function call, check if the argument is one or more specified strings. Track both module+offsets and absolute addresses for calls where this occurs. Report the module+offsets and absolute addresses where ALL strings are observed. + +Arguments +--------- + +`output_file`: Optional, path to store results at. Default is `findcall.txt` in the current directory + +Dependencies +------------ +`callwitharg` must be loaded and configured with the target strings you're looking for +`callstack_instr` + +APIs and Callbacks +------------------ + +Example +------- diff --git a/panda/plugins/findcall/findcall.cpp b/panda/plugins/findcall/findcall.cpp new file mode 100644 index 00000000000..69db549a7d8 --- /dev/null +++ b/panda/plugins/findcall/findcall.cpp @@ -0,0 +1,157 @@ +#include "panda/plugin.h" +#include "panda/plugin_plugin.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "callstack_instr/callstack_instr.h" +#include "callwitharg/callwitharg.h" +#include "osi/osi_types.h" + +extern "C" { +//#include "callwitharg/callwitharg_int_fns.h" +#include "callwitharg/callwitharg_ext.h" +#include "osi/osi_ext.h" + bool init_plugin(void *); + void uninit_plugin(void *); +} + +//size_t target_str_len; +//char *target_str; +std::ofstream outfile; + +// We want to store a mapping from unique strigs -> set of (module+offset (as a tuple)) +std::map>> matches; + +// Also we store unique strings -> absolute addresses +std::map> matches_abs; + +void on_match(CPUState* cpu, target_ulong func_addr, target_ulong *args, char* value, uint matching_idx, uint args_read) { + assert(args_read >= 1); + + // A match happend! Use OSI to get our current module+offst + OsiProc *current = get_current_process(cpu); + + OsiModule *m = get_mapping_by_addr(cpu, current, func_addr); + if (m) { + // If matches doesn't have a key for this string, add it + if (matches.find(value) == matches.end()) { + matches[value] = std::set>(); + } + + if (matches_abs.find(value) == matches_abs.end()) { + matches_abs[value] = std::set(); + } + + // Now check the matches[value] set for this module+offset tuple - if it's not there, add it + std::tuple t(m->name, func_addr - m->base); + if (matches[value].find(t) == matches[value].end()) { + matches[value].insert(t); + printf("Match of %s at func_addr " TARGET_FMT_lx " which is in module %s + " TARGET_FMT_lx "\n", value, func_addr, m->name, func_addr - m->base); + } + + if (matches_abs[value].find(func_addr) == matches_abs[value].end()) { + matches_abs[value].insert(func_addr); + printf("Match of %s at func_addr " TARGET_FMT_lx "\n", value, func_addr); + } + + // cleanup + free_osimodule(m); + }else { + printf("Match of %s at unknown\n", value); + } +} + +// logfile default is cwd/findcall.txt +std::filesystem::path logfile = std::filesystem::current_path() / "findcall.txt"; + +bool init_plugin(void *self) { + init_callwitharg_api(); + init_osi_api(); + + std::unique_ptr args( + panda_get_args("findcall"), panda_free_args); + + const char* logfile_arg = panda_parse_string_opt(args.get(), "output_file", + NULL, "Output file to record compared values into"); + if (logfile_arg) logfile = std::string(logfile_arg); + + /* + target_str = strdup(panda_parse_string_req(args.get(), "target_str", "String to match")); + target_str_len = strlen(target_str); + + if (target_str_len <= 0) { + printf("targetcmp error: invalid target_str argument\n"); + return false; + } + */ + +#if defined(TARGET_ARM) || defined(TARGET_MIPS) || defined(TARGET_X86_64) + outfile.open(logfile.string(), std::ios_base::out | std::ios_base::trunc); // Empty file to start + + // Tell callwitharg to call on_match when it finds a match of our string + //add_target_string(target_str); + + // Register on_call_match with callwitharg's on_call_match_str PPP callback + PPP_REG_CB("callwitharg", on_call_match_str, on_match); + return true; +#endif + printf("ERROR: Unsupported architecture for targetcmp\n"); + return false; +} + +void uninit_plugin(void *self) { + // Find all unique module+offset values in matches, then check if all keys ever have each value - if so print + std::map, std::set> modoff_to_keys; + for (auto const& [key, val] : matches) { + for (auto const& [mod, off] : val) { + modoff_to_keys[std::make_tuple(mod, off)].insert(key); + } + } + // Now we have a map from module+offset to set of keys that have that module+offset - print all that have all keys + for (auto const& [modoff, keys] : modoff_to_keys) { + if (keys.size() == matches.size()) { + + // Write module, offset to outfile + outfile << std::get<0>(modoff) << "," << std::get<1>(modoff) << std::endl; + + // Print the common module+offset then the two keys + printf("%s + " TARGET_FMT_lx "\n", std::get<0>(modoff).c_str(), std::get<1>(modoff)); + for (auto const& key : keys) { + printf(" %s\n", key.c_str()); + } + } + } + + // Now write down all absolute addresses that are in every key + std::map> abs_to_keys; + for (auto const& [key, val] : matches_abs) { + for (auto const& abs : val) { + abs_to_keys[abs].insert(key); + } + } + // Now we have a map from absolute address to set of keys that have that absolute address - print all that have all keys + for (auto const& [abs, keys] : abs_to_keys) { + if (keys.size() == matches_abs.size()) { + + // Write absolute address to outfile: just raw address + outfile << abs << std::endl; + + // Print the common absolute address then the two keys + printf(TARGET_FMT_lx "\n", abs); + for (auto const& key : keys) { + printf(" %s\n", key.c_str()); + } + } + } + + if (outfile.is_open()) { + outfile.close(); + } + +} diff --git a/panda/plugins/loaded/loaded.cpp b/panda/plugins/loaded/loaded.cpp index 844ec55be0a..f3303c5fb73 100644 --- a/panda/plugins/loaded/loaded.cpp +++ b/panda/plugins/loaded/loaded.cpp @@ -69,7 +69,7 @@ uint32_t guest_strncpy(CPUState *cpu, char *buf, size_t maxlen, target_ulong gue buf[0] = 0; unsigned i; for (i=0; i +#include +#include +#include +#include +#include +#include + +#include "callstack_instr/callstack_instr.h" +#include "callwitharg/callwitharg.h" // Unnecessary? + +extern "C" { +#include "callwitharg/callwitharg_ext.h" + bool init_plugin(void *); + void uninit_plugin(void *); +} + +size_t target_str_len; +char *target_str; +std::ofstream outfile; + +// We track the last QUEUE_SIZE addresses we've checked to avoid rereading guest pointers +#define QUEUE_SIZE 100 +std::atomic queue_idx(0); +std::atomic queue[QUEUE_SIZE]; +// Now we'll define a function to add to the queue +void add_to_queue(target_ulong addr) { + size_t idx = queue_idx.fetch_add(1); + queue[idx % QUEUE_SIZE] = addr; +} +// And a function to check if an address is in the queue +bool in_queue(target_ulong addr) { + for (size_t i = 0; i < QUEUE_SIZE; i++) { + if (queue[i] == addr) return true; + } + return false; +} + +// C++ set for storing unique string matches +std::set matches; + +void record_match(char *str) { + if (strlen(str) == 0) return; + + for (int i = 0; i < strlen(str); i++) { + if (!isprint(str[i])) { + return; + } + } + + std::string s(str); + if (matches.find(s) == matches.end()) { + //printf("TargetCMP finds %s with length %u\n", s.c_str(), s.length()); + outfile << s << std::endl; + matches.insert(s); + } +} + +void on_match(CPUState* cpu, target_ulong func_addr, target_ulong *args, char* value, uint matching_idx, uint args_read) { + // We expect 2 args, if matching_idx is 0, arg1 is our target pointer, otherwise arg0 + assert(args_read >= 2); + + //printf("Match in arg %d with arg1=" TARGET_FMT_lx " and arg2=" TARGET_FMT_lx "\n", matching_idx, args[0], args[1]); + + target_ulong target_ptr = args[matching_idx == 0 ? 1 : 0]; // If we matched arg0, we want arg1 and vice versa + + // If it's in the queue, we've already checked it - bail + if (in_queue(target_ptr)) { + return; + } + // Otherwise add it to the queue + add_to_queue(target_ptr); + + size_t short_len = strlen(value); + size_t full_len = 4*short_len; + char* other_arg = (char*)malloc(full_len + 1); + + // Try to read the target string from memory + if (panda_virtual_memory_read(cpu, target_ptr, (uint8_t*)other_arg, full_len) == 0) { + other_arg[target_str_len] = '\0'; // Ensure null termination + } else if (panda_virtual_memory_read(cpu, target_ptr, (uint8_t*)other_arg, short_len) == 0) { + // Recovered short string - move null terminator early + other_arg[short_len] = '\0'; // Ensure null termination + } else { + // Failed to read even the short string - bail + free(other_arg); + return; + } + record_match(other_arg); + free(other_arg); +} + +// logfile default is cwd/targetcmp.txt +std::filesystem::path logfile = std::filesystem::current_path() / "targetcmp.txt"; + +bool init_plugin(void *self) { + if (!init_callwitharg_api()) { + printf("[targetcmp] Fatal error: unable to initialize callwitharg - is it loaded?\n"); + return false; + } + + std::unique_ptr args( + panda_get_args("targetcmp"), panda_free_args); + + const char* logfile_arg = panda_parse_string_opt(args.get(), "output_file", + NULL, "Output file to record compared values into"); + if (logfile_arg) logfile = std::string(logfile_arg); + + target_str = strdup(panda_parse_string_req(args.get(), "target_str", "String to match")); + target_str_len = strlen(target_str); + + if (target_str_len <= 0) { + printf("targetcmp error: invalid target_str argument\n"); + return false; + } + + // On every function call, use our callback to check an argument is the target_str, if so store the other arg +#if defined(TARGET_ARM) || defined(TARGET_MIPS) || defined(TARGET_X86_64) + // Create empty file - Just so we see that something's happening + // Open file for writing, delete anything there. + outfile.open(logfile.string(), std::ios_base::out | std::ios_base::trunc); + + // Call callwitharg's add_target_string function + add_target_string(target_str); + + // Register on_call_match with callwitharg's on_call_match_str PPP callback + PPP_REG_CB("callwitharg", on_call_match_str, on_match); + return true; +#endif + printf("ERROR: Unsupported architecture for targetcmp\n"); + return false; +} + +void uninit_plugin(void *self) { + if (outfile.is_open()) { + outfile.close(); + } + free((void*)target_str); +} diff --git a/panda/python/core/pandare/panda.py b/panda/python/core/pandare/panda.py index 3a2fbd6f642..63af77cf5f6 100644 --- a/panda/python/core/pandare/panda.py +++ b/panda/python/core/pandare/panda.py @@ -2634,7 +2634,7 @@ def interact(self, confirm_quit=True): interface after this returns ''' print("PANDA: entering interactive mode. Type pandaquit to exit") - prompt = self.expect_prompt.decode("utf8") if self.expect_prompt else "$ " + prompt = self.expect_prompt.decode("utf8") if self.expect_prompt and isinstance(self.expect_prompt, bytes) else "$ " if not prompt.endswith(" "): prompt += " " while True: cmd = input(prompt) # TODO: Strip all control characters - Ctrl-L breaks things diff --git a/panda/python/examples/cb_qmp.py b/panda/python/examples/cb_qmp.py new file mode 100644 index 00000000000..00ea50b7f57 --- /dev/null +++ b/panda/python/examples/cb_qmp.py @@ -0,0 +1,45 @@ +from pandare import Panda +from time import sleep +import json +import tempfile + +path = tempfile.mktemp(".sock") +panda = Panda(generic="x86_64", extra_args=["-qmp", f"unix:{path},server,nowait"]) + +print() +print("QMP example running! To send a QMP command to this VM, run:") +print("""echo '{ "execute": "mycmd", "arguments": { "arg_key" : "arg_val" } }' | socat - UNIX-CONNECT:""" + path + """ | cat""") +print() + +@panda.cb_qmp +def on_qmp_command(cmd, arg_json, result_json): + args = {} + cmd = panda.ffi.string(cmd).decode() if cmd != panda.ffi.NULL else None + if cmd != 'mycmd': + return False + if arg_json != panda.ffi.NULL: + args = json.loads(panda.ffi.string(arg_json).decode()) + + print(f"PyPANDA handling QMP command {cmd} with args {args}") + + data = { + "hello": "world", + "key": "value", + "0": 1, + } + + # Dump our result to json and copy it into the result_json buffer + encoded_data = json.dumps(data).encode() + result_buffer = panda.ffi.new("char[]", len(encoded_data) + 1) # Null term + panda.ffi.memmove(result_buffer, encoded_data, len(encoded_data)) + result_json[0] = result_buffer + return True + +@panda.queue_blocking +def driver(): + panda.revert_sync("root") + panda.run_serial_cmd("whoami") + sleep(300) + panda.end_analysis() + +panda.run() diff --git a/panda/python/tests/run_all_tests.sh b/panda/python/tests/run_all_tests.sh new file mode 100644 index 00000000000..f05a5a0d754 --- /dev/null +++ b/panda/python/tests/run_all_tests.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Define the list of test scripts to run +declare -a tests=("dyn_hooks" "copy_test" "file_fake" "file_hook" "generic_tests" "monitor_cmds" "multi_proc_cbs" "sleep_in_cb" "syscalls" "record_no_snap" "sig_suppress") + +# Base directory for test scripts +TEST_DIR="/panda/panda/python/tests" + +# Iterate over the test scripts array +for test_script in "${tests[@]}"; do + # Construct the full path to the script + full_script_path="$TEST_DIR/${test_script}.py" + + echo "Running $full_script_path..." + python3 "$full_script_path" + + # Check the exit status of the script + if [ $? -ne 0 ]; then + echo "Test $full_script_path failed" + exit 1 + fi +done diff --git a/panda/scripts/install_ubuntu.sh b/panda/scripts/install_ubuntu.sh index 6442f351171..d54c40a4898 100755 --- a/panda/scripts/install_ubuntu.sh +++ b/panda/scripts/install_ubuntu.sh @@ -110,7 +110,7 @@ if [[ !$(ldconfig -p | grep -q libcapstone.so.4) ]]; then echo "Installing libcapstone v4" pushd /tmp && \ curl -o /tmp/cap.tgz -L https://github.com/aquynh/capstone/archive/4.0.2.tar.gz && \ - tar xvf cap.tgz && cd capstone-4.0.2/ && ./make.sh && $SUDO make install && cd /tmp && \ + tar xvf cap.tgz && cd capstone-4.0.2/ && MAKE_JOBS=$(nproc) ./make.sh && $SUDO make install && cd /tmp && \ rm -rf /tmp/capstone-4.0.2 $SUDO ldconfig popd diff --git a/panda/src/callbacks.c b/panda/src/callbacks.c index e8f5d6790e5..73ec90c1bb0 100644 --- a/panda/src/callbacks.c +++ b/panda/src/callbacks.c @@ -57,13 +57,8 @@ int panda_argc; int nb_panda_plugins = 0; panda_plugin panda_plugins[MAX_PANDA_PLUGINS]; -bool panda_plugins_to_unload[MAX_PANDA_PLUGINS]; - bool panda_plugin_to_unload = false; -int nb_panda_plugins_loaded = 0; -char *panda_plugins_loaded[MAX_PANDA_PLUGINS]; - bool panda_please_flush_tb = false; bool panda_please_break_exec = false; bool panda_update_pc = false; @@ -113,59 +108,96 @@ static char* attempt_normalize_path(char* path){ return new_path; } -bool panda_load_external_plugin(const char *filename, const char *plugin_name, void *plugin_uuid, void *init_fn_ptr) { - // don't load the same plugin twice - char* rfilename = attempt_normalize_path(strdup(filename)); - uint32_t i; - for (i=0; i dlopen(filename, RTLD_NOW); - bool (*init_fn)(void *) = init_fn_ptr; //normally dlsym init_fun + return libpanda; +} - // Populate basic plugin info *before* calling init_fn. - // This allows plugins accessing handles of other plugins before - // initialization completes. E.g. osi does a panda_require("win7x86intro"), - // and then win7x86intro does a PPP_REG_CB("osi", ...) while initializing. - panda_plugins[nb_panda_plugins].plugin = plugin; - if (plugin_name) { - strncpy(panda_plugins[nb_panda_plugins].name, plugin_name, 256); +// When running as a library, load libpanda +static bool load_libpanda(void) { + + const char *panda_lib = g_getenv("PANDA_LIB"); + void *libpanda; + + if(panda_lib != NULL) { + libpanda = try_open_libpanda(panda_lib); } else { - char *pn = g_path_get_basename(rfilename); - *g_strrstr(pn, HOST_DSOSUF) = '\0'; - strncpy(panda_plugins[nb_panda_plugins].name, pn, 256); - g_free(pn); +#ifndef LIBRARY_DIR + assert(0 && "LIBRARY_DIR undefined, but library mode is enabled - Unsupported architecture?"); + LOG_ERROR(PANDA_MSG_FMT "LIBRARY_DIR not defined\n", PANDA_CORE_NAME); +#endif + const char *lib_dir = g_getenv("PANDA_DIR"); + + if (lib_dir != NULL) { + panda_lib = g_strdup_printf("%s%s", lib_dir, LIBRARY_DIR); + } else { + LOG_WARNING(PANDA_MSG_FMT "WARNING: using hacky dlopen code that will be removed soon\n", PANDA_CORE_NAME); + panda_lib = g_strdup_printf("../../../build/%s", LIBRARY_DIR); // XXX This is bad, need a less hardcoded path + } + + libpanda = try_open_libpanda(panda_lib); + g_free((char *)panda_lib); } - nb_panda_plugins++; - // Call init_fn and check status. - LOG_INFO(PANDA_MSG_FMT "initializing %s\n", PANDA_CORE_NAME, panda_plugins[nb_panda_plugins-1].name); - panda_help_wanted = false; - panda_args_set_help_wanted(plugin_name); - if (panda_help_wanted) { - printf("Options for plugin %s:\n", plugin_name); - fprintf(stderr, "PLUGIN ARGUMENT REQUIRED DESCRIPTION\n"); - fprintf(stderr, "====== ======== ======== ===========\n"); + return libpanda != NULL; +} + +// Internal: remove a plugin from the global array panda_plugins +static void panda_delete_plugin(int i) +{ + g_free(panda_plugins[i].name); + if (i != nb_panda_plugins - 1) { // not the last element + memmove(&panda_plugins[i], &panda_plugins[i + 1], + (nb_panda_plugins - i - 1) * sizeof(panda_plugin)); } - if(!init_fn(plugin) || panda_plugin_load_failed) { - return false; + nb_panda_plugins--; +} + +static void dlclose_plugin(int plugin_idx) { + void *plugin = panda_plugins[plugin_idx].plugin; + bool exported_symbols = panda_plugins[plugin_idx].exported_symbols; + panda_delete_plugin(plugin_idx); + dlclose(plugin); + if(exported_symbols) { + // This plugin was dlopened twice. dlclose it twice to fully unload it. + dlclose(plugin); } - return true; } +// Determine if the plugin being loaded wants to export symbols to +// subsequently loaded plugins. If it does, dlopen it a second time +// with RTLD_GLOBAL. +static void do_check_export_symbols(panda_plugin *panda_plugin, const char *filename) { -bool panda_load_plugin(const char *filename, const char *plugin_name) { - return _panda_load_plugin(filename, plugin_name, false); + char *export_symbol = g_strdup_printf("PANDA_EXPORT_SYMBOLS_%s", panda_plugin->name); + + if(NULL != dlsym(panda_plugin->plugin, export_symbol)) { + LOG_DEBUG(PANDA_MSG_FMT "Exporting symbols for plugin %s\n", PANDA_CORE_NAME, panda_plugin->name); + assert(panda_plugin->plugin == dlopen(filename, RTLD_NOW | RTLD_GLOBAL)); + panda_plugin->exported_symbols = true; + } else { + // Error condition is not unexpected, clear dlerror(), + // otherwise someone might call it later and be confused + dlerror(); + } + + g_free(export_symbol); } -bool _panda_load_plugin(const char *filename, const char *plugin_name, bool library_mode) { +static bool _panda_load_plugin(const char *filename, const char *plugin_name, bool library_mode) { + + static bool libpanda_loaded = false; + + if ((plugin_name == NULL) || (*plugin_name == '\0')) { + LOG_ERROR(PANDA_MSG_FMT "Fatal error: plugin_name is required\n", PANDA_CORE_NAME); + abort(); + } #ifndef CONFIG_LLVM // Taint2 seems to be our most commonly used LLVM plugin and it causes some confusion @@ -175,86 +207,54 @@ bool _panda_load_plugin(const char *filename, const char *plugin_name, bool libr } #endif - if (filename == NULL) { + if ((filename == NULL) || (*filename == '\0')) { LOG_ERROR(PANDA_MSG_FMT "Fatal error: could not find path for plugin %s\n", PANDA_CORE_NAME, plugin_name); + abort(); } - assert(filename != NULL); // don't load the same plugin twice uint32_t i; - for (i=0; iplugin_name, argname, help); + printf("%-20s%-24sOptional %s (default=true)\n", args->plugin_name, argname, help); } // not found @@ -1172,13 +1152,13 @@ static target_ulong panda_parse_ulong_internal(panda_arg_list *args, const char error_handling: if (required) { LOG_ERROR("ERROR: plugin required ulong argument \"%s\" but you did not provide it\n", argname); - fprintf(stderr, "Help for \"%s\": %s\n", argname, help); + LOG_ERROR(PANDA_MSG_FMT "Help for \"%s\": %s\n", PANDA_CORE_NAME, argname, help); panda_plugin_load_failed = true; } help: if (panda_help_wanted) { - if (required) fprintf(stderr, "%-20s%-24sRequired %s\n", args->plugin_name, argname, help); - else fprintf(stderr, "%-20s%-24sOptional %s (default=" TARGET_FMT_ld ")\n", args->plugin_name, argname, help, defval); + if (required) printf("%-20s%-24sRequired %s\n", args->plugin_name, argname, help); + else printf("%-20s%-24sOptional %s (default=" TARGET_FMT_ld ")\n", args->plugin_name, argname, help, defval); } return defval; @@ -1214,8 +1194,8 @@ static uint32_t panda_parse_uint32_internal(panda_arg_list *args, const char *ar } help: if (panda_help_wanted) { - if (required) fprintf(stderr, "%-20s%-24sRequired %s\n", args->plugin_name, argname, help); - else fprintf(stderr, "%-20s%-24sOptional %s (default=%d)\n", args->plugin_name, argname, help, defval); + if (required) printf("%-20s%-24sRequired %s\n", args->plugin_name, argname, help); + else printf("%-20s%-24sOptional %s (default=%d)\n", args->plugin_name, argname, help, defval); } return defval; @@ -1251,8 +1231,8 @@ static uint64_t panda_parse_uint64_internal(panda_arg_list *args, const char *ar } help: if (panda_help_wanted) { - if (required) fprintf(stderr, "%-20s%-24sRequired %s)\n", args->plugin_name, argname, help); - else fprintf(stderr, "%-20s%-24sOptional %s (default=%" PRId64 ")\n", args->plugin_name, argname, help, defval); + if (required) printf("%-20s%-24sRequired %s)\n", args->plugin_name, argname, help); + else printf("%-20s%-24sOptional %s (default=%" PRId64 ")\n", args->plugin_name, argname, help, defval); } return defval; @@ -1288,8 +1268,8 @@ static double panda_parse_double_internal(panda_arg_list *args, const char *argn } help: if (panda_help_wanted) { - if (required) fprintf(stderr, "%-20s%-24sRequired %s\n", args->plugin_name, argname, help); - else fprintf(stderr, "%-20s%-24sOptional %s (default=%f)\n", args->plugin_name, argname, help, defval); + if (required) printf("%-20s%-24sRequired %s\n", args->plugin_name, argname, help); + else printf("%-20s%-24sOptional %s (default=%f)\n", args->plugin_name, argname, help, defval); } return defval; @@ -1363,8 +1343,8 @@ static const char *panda_parse_string_internal(panda_arg_list *args, const char } help: if (panda_help_wanted) { - if (required) fprintf(stderr, "%-20s%-24sRequired %s\n", args->plugin_name, argname, help); - else fprintf(stderr, "%-20s%-24sOptional %s (default=\"%s\")\n", args->plugin_name, argname, help, defval); + if (required) printf("%-20s%-24sRequired %s\n", args->plugin_name, argname, help); + else printf("%-20s%-24sOptional %s (default=\"%s\")\n", args->plugin_name, argname, help, defval); } return defval; diff --git a/panda/src/cb-support.c b/panda/src/cb-support.c index ecf4c1d718f..0f72386ed18 100644 --- a/panda/src/cb-support.c +++ b/panda/src/cb-support.c @@ -101,7 +101,9 @@ void panda_cb_trampoline_start_block_exec(void* context, CPUState *cpu, Translat // these aren't used MAKE_CALLBACK(void, HD_READ, hd_read, CPUState*, env); MAKE_CALLBACK(void, HD_WRITE, hd_write, CPUState*, env); + MAKE_CALLBACK(int, MONITOR, monitor, Monitor*, mon, const char*, cmd); +MAKE_CALLBACK(bool, QMP, qmp, char*, cmd, char*, args, char **, result); // Helper - get a physical address static inline hwaddr get_paddr(CPUState *cpu, target_ptr_t addr, void *ram_ptr) { @@ -196,13 +198,18 @@ MAKE_CALLBACK_NO_ARGS(void, PRE_SHUTDOWN, pre_shutdown); // Non-standard callbacks below here +extern bool panda_plugin_to_unload; +extern int nb_panda_plugins; +extern panda_plugin panda_plugins[MAX_PANDA_PLUGINS]; + void PCB(before_find_fast)(void) { if (panda_plugin_to_unload) { panda_plugin_to_unload = false; - for (int i = 0; i < MAX_PANDA_PLUGINS; i++) { - if (panda_plugins_to_unload[i]) { + for (int i = 0; i < nb_panda_plugins;) { + if (panda_plugins[i].unload) { panda_do_unload_plugin(i); - panda_plugins_to_unload[i] = false; + } else { + i++; } } } diff --git a/panda/src/panda_api.c b/panda/src/panda_api.c index a43be94e591..36bce437597 100644 --- a/panda/src/panda_api.c +++ b/panda/src/panda_api.c @@ -200,56 +200,6 @@ void panda_setup_signal_handling(void (*f) (int, void*, void *)) panda_external_signal_handler = (void(*)(int,siginfo_t*,void*)) f; } -// we have this temporarily in callbacks.c -> to be moved here -/* -bool panda_load_external_plugin(const char *filename, const char *plugin_name, void *plugin_uuid, void *init_fn_ptr) { - // don't load the same plugin twice - uint32_t i; - for (i=1; i dlopen(filename, RTLD_NOW); - bool (*init_fn)(void *) = init_fn_ptr; //normally dlsym init_fun - - // Populate basic plugin info *before* calling init_fn. - // This allows plugins accessing handles of other plugins before - // initialization completes. E.g. osi does a panda_require("win7x86intro"), - // and then win7x86intro does a PPP_REG_CB("osi", ...) while initializing. - panda_plugins[nb_panda_plugins].plugin = plugin; - if (plugin_name) { - strncpy(panda_plugins[nb_panda_plugins].name, plugin_name, 256); - } else { - char *pn = g_path_get_basename((char *) filename); - *g_strrstr(pn, HOST_DSOSUF) = '\0'; - strncpy(panda_plugins[nb_panda_plugins].name, pn, 256); - g_free(pn); - } - nb_panda_plugins++; - - // Call init_fn and check status. - fprintf(stderr, PANDA_MSG_FMT "initializing %s\n", PANDA_CORE_NAME, panda_plugins[nb_panda_plugins-1].name); - panda_help_wanted = false; - panda_args_set_help_wanted(plugin_name); - if (panda_help_wanted) { - printf("Options for plugin %s:\n", plugin_name); - fprintf(stderr, "PLUGIN ARGUMENT REQUIRED DESCRIPTION\n"); - fprintf(stderr, "====== ======== ======== ===========\n"); - } - if(!init_fn(plugin) || panda_plugin_load_failed) { - return false; - } - return true; -}*/ - - - // Taken from Avatar2's Configurable Machine - see hw/avatar/configurable_machine.c void map_memory(char* name, uint64_t size, uint64_t address) { //const char * name; /// XXX const? diff --git a/panda/src/rr/panda_rr2.c b/panda/src/rr/panda_rr2.c index 08310cdf2b9..0355fec867f 100644 --- a/panda/src/rr/panda_rr2.c +++ b/panda/src/rr/panda_rr2.c @@ -6,7 +6,7 @@ #include #include -#include +#include #include "config-host.h" #include "panda/rr/panda_rr2.h" @@ -15,7 +15,7 @@ bool write_file_to_archive(struct rr_file_state* rstate, const char* fname, const uint8_t* contents, size_t len); void write_magic_file(struct rr_file_state* rstate); -void write_hash_to_log(struct rr_file_state* rstate, const char* fname, SHA_CTX* ctx); +void write_hash_to_log(struct rr_file_state* rstate, const char* fname, EVP_MD_CTX* mdctx); bool is_valid_rrv2_file(const char* state); void add_file_hash_for_content(struct rr_file_state* rstate, const char* fname, const void* content, size_t len); @@ -287,12 +287,13 @@ bool rrfile_add_recording_file(struct rr_file_state* rstate, const char* type, archive_entry_free(entry); } - // Initialize a SHA_CTX for openssl for this file - SHA_CTX* ctx = (SHA_CTX*)malloc(sizeof(SHA_CTX)); - if (!ctx || !SHA1_Init(ctx)) { + // Initialize a EVP_MD_CTX for openssl for this file + EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); + const EVP_MD *md = EVP_sha1(); + if (!mdctx || !md || !EVP_DigestInit_ex(mdctx, md, NULL)) { fprintf(stderr, "Failed to find hash for file contents of %s\n", type); - if (ctx) { - free(ctx); + if (mdctx) { + EVP_MD_CTX_destroy(mdctx); } return false; } @@ -302,7 +303,7 @@ bool rrfile_add_recording_file(struct rr_file_state* rstate, const char* type, uint8_t buffer[1024 * 1024]; len = fread(buffer, 1, sizeof(buffer), fp); while (len > 0) { - SHA1_Update(ctx, buffer, len); + EVP_DigestUpdate(mdctx, buffer, len); int status = archive_write_data(a, buffer, len); if (status <= 0) { fprintf(stderr, "Failed to archive_write_data\n"); @@ -315,8 +316,8 @@ bool rrfile_add_recording_file(struct rr_file_state* rstate, const char* type, } unlink(fpath); // Write the hash for this file out to the log - write_hash_to_log(rstate, type, ctx); - free(ctx); + write_hash_to_log(rstate, type, mdctx); + EVP_MD_CTX_destroy(mdctx); return true; } @@ -418,7 +419,7 @@ bool write_file_to_archive(struct rr_file_state* rstate, const char* fname, return true; } -void write_hash_to_log(struct rr_file_state* rstate, const char* fname, SHA_CTX* ctx) +void write_hash_to_log(struct rr_file_state* rstate, const char* fname, EVP_MD_CTX* mdctx) { // If the log isn't open for writing, exit if (!rstate->hash_fp) { @@ -426,11 +427,11 @@ void write_hash_to_log(struct rr_file_state* rstate, const char* fname, SHA_CTX* } size_t hexsize = 41; - unsigned char* hash_md = (unsigned char*)malloc(SHA_DIGEST_LENGTH); + unsigned char* hash_md = (unsigned char*)malloc(EVP_MAX_MD_SIZE); char* hex = (char*)calloc(1, hexsize); // Finalize the hash and snprintf the hexified string for it - if (!SHA1_Final(hash_md, ctx)) { + if (!EVP_DigestFinal_ex(mdctx, hash_md, 0)) { fprintf(stderr, "Failed to find hash for file contents of %s\n", fname); goto cleanup; } @@ -453,23 +454,24 @@ void write_hash_to_log(struct rr_file_state* rstate, const char* fname, SHA_CTX* void add_file_hash_for_content(struct rr_file_state* rstate, const char* fname, const void* content, size_t len) { - // Initialize a SHA_CTX for openssl - SHA_CTX* ctx = (SHA_CTX*)malloc(sizeof(SHA_CTX)); - if (!ctx || !SHA1_Init(ctx)) { + // Initialize a EVP_MD_CTX for openssl + EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); + const EVP_MD *md = EVP_sha1(); + if (!mdctx || !md || !EVP_DigestInit_ex(mdctx, md, NULL)) { fprintf(stderr, "Failed to find hash for file contents of %s\n", fname); goto cleanup; } // Calculate the SHA1 hash for these file contents - if (!SHA1_Update(ctx, content, len)) { + if (!EVP_DigestUpdate(mdctx, content, len)) { fprintf(stderr, "Failed to find hash for file contents of %s\n", fname); goto cleanup; } // Write it to the log - write_hash_to_log(rstate, fname, ctx); + write_hash_to_log(rstate, fname, mdctx); cleanup: - if (ctx) { - free(ctx); + if (mdctx) { + EVP_MD_CTX_destroy(mdctx); } } diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index dc502129d80..8311ff99bce 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -20,6 +20,11 @@ #include "qapi-types.h" #include "qapi/qmp/qerror.h" +//#include "panda/callbacks/cb-support.h" +extern bool panda_callbacks_qmp(char *command, char* args, char **result); +// And we have a stub in stubs/qtest.c that always returns false if we build +// without panda for tests + static QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) { const QDictEntry *ent; @@ -83,24 +88,55 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, command = qdict_get_str(dict, "execute"); cmd = qmp_find_command(cmds, command); + + if (!qdict_haskey(dict, "arguments")) { + args = qdict_new(); + } else { + args = qdict_get_qdict(dict, "arguments"); + QINCREF(args); + } + if (cmd == NULL) { + // Call any PANDA consumers of the unhandled command + // Provide them with arguments in json format. + // If any plugin returns true, we assume it handled the command + // and we expect a json output in result. + char *result = NULL; + const QString* cmd_args_q = qobject_to_json(QOBJECT(args)); + const char *cmd_args = qstring_get_str(cmd_args_q); + + if (panda_callbacks_qmp((char*)command, (char*)cmd_args, &result)) { + if (result != NULL) { + // We have a return value from the callback. Let's convert it to a qobject + ret = qobject_from_json(result, &local_err); + if (local_err) { + printf("PANDA ERROR decoding result json in callback"); + error_propagate(errp, local_err); + return NULL; + } + } + + if (!ret) { + printf("PANDA WARNING: a qmp callback consumer returned TRUE without providing \ + a return value! Creating empty dictionary"); + + ret = QOBJECT(qdict_new()); + } + QDECREF(args); + return ret; + } + error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, "The command %s has not been found", command); return NULL; } + if (!cmd->enabled) { error_setg(errp, "The command %s has been disabled for this instance", command); return NULL; } - if (!qdict_haskey(dict, "arguments")) { - args = qdict_new(); - } else { - args = qdict_get_qdict(dict, "arguments"); - QINCREF(args); - } - cmd->fn(args, &ret, &local_err); if (local_err) { error_propagate(errp, local_err); diff --git a/stubs/qtest.c b/stubs/qtest.c index 891eb954fbe..f68f66e9395 100644 --- a/stubs/qtest.c +++ b/stubs/qtest.c @@ -18,3 +18,9 @@ bool qtest_driver(void) { return false; } + +/* Needed for qmp-dispatch tests to pass when we don't have panda */ +bool panda_callbacks_qmp(char *command, char* args, char **result); +bool panda_callbacks_qmp(char *command, char* args, char **result) { + return false; +}