VYPR
Low severity3.3NVD Advisory· Published May 29, 2026· Updated May 29, 2026

CVE-2026-45324

CVE-2026-45324

Description

Rizin is a UNIX-like reverse engineering framework and command-line toolset. There is a double free in librz/core/cmd/cmd_search.c:byte_pattern_search() due wrong pointer ownership declared. This vulnerability is fixed by commit 045fff363b42b8a6dda8ad5229c29ec3267e7dbe.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Rizin byte_pattern_search() contains a double free due to wrong pointer ownership, leading to crashes or potential memory corruption.

Vulnerability

A double-free vulnerability exists in Rizin's byte_pattern_search() function within librz/core/cmd/cmd_search.c. The root cause is incorrect pointer ownership handling, which causes a previously freed pointer to be freed again when an error path is taken during search operations. All versions prior to the commit 045fff363b42b8a6dda8ad5229c29ec3267e7dbe are affected [1][2].

Exploitation

An attacker can trigger the double free by providing input that leads to invalid search boundaries (e.g., using an unsupported or malformed search.in region). This causes the function to enter its error handling code, where the free operation is applied to an already freed pointer. No authentication is required, but the attacker must be able to execute search commands with crafted parameters, typically requiring local access or indirect interaction with the tool [2].

Impact

Successful exploitation results in an application crash (denial of service) due to heap corruption. Under certain conditions, the memory corruption could potentially be leveraged for more severe outcomes such as arbitrary code execution, though the low CVSS score (3.3) indicates limited practical likelihood. The primary risk is disruption of the reverse engineering workflow [2].

Mitigation

The vulnerability is fixed in commit 045fff363b42b8a6dda8ad5229c29ec3267e7dbe, released on 2026-05-29 [1]. Users should update Rizin to a build that includes this commit. As a workaround, avoid using search commands with invalid or untrusted boundary values, and validate the search.in parameter against the documented list of valid modes [2]. This CVE is not listed in CISA's Known Exploited Vulnerabilities catalog.

AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Rizin/Rizinreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)

Patches

1
045fff363b42

Fix double free and reject invalid values for `search.in` (#6327)

https://github.com/rizinorg/rizinRot127May 11, 2026via text-mined
6 files changed · +85 49
  • librz/core/cbounds.c+56 42 modified
    @@ -650,76 +650,90 @@ RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_get_boundaries_debug_program(RZ_NO
      * \brief      Returns a list of boundaries (as RzIOMap), based on the selected mode; see [search/analysis/zoom/[in/from/to]] for available modes.
      *
      * \param      core      The RzCore to use
    - * \param      from_key  The [search|analysis|zoom].from keyword to use in RzConfig
    - * \param      to_key    The [search|analysis|zoom].to keyword to use in RzConfig
    - * \param      in_key    The [search|analysis|zoom].in keyword to use in RzConfig
    + * \param      interval  The address range of the boundaries.
    + * \param      region    The region of the boundaries. Check valid ones with `e search.in=?`.
      *
      * \return     On success a valid pointer (can be an empty list), otherwise NULL
      */
    -RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_get_boundaries_select(RZ_NONNULL RzCore *core, RZ_NONNULL const char *from_key, RZ_NONNULL const char *to_key, RZ_NONNULL const char *in_key) {
    -	rz_return_val_if_fail(core && from_key && to_key && in_key, NULL);
    -
    -	RzInterval interval;
    -	ut64 from = rz_config_get_i(core->config, from_key);
    -	ut64 to = rz_config_get_i(core->config, to_key);
    -	const char *use_mode = rz_config_get(core->config, in_key);
    -
    -	interval.addr = from;
    -	interval.size = to - from;
    -
    -	if (!strcmp(use_mode, "raw") || !strcmp(use_mode, "file")) {
    +RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_get_boundaries(RZ_NONNULL RzCore *core, RzInterval interval, const char *region) {
    +	rz_return_val_if_fail(core, NULL);
    +	if (!strcmp(region, "raw") || !strcmp(region, "file")) {
     		return rz_core_get_boundaries_raw(core, interval);
    -	} else if (!strcmp(use_mode, "block")) {
    +	} else if (!strcmp(region, "block")) {
     		return rz_core_get_boundaries_block(core, interval);
    -	} else if (!strcmp(use_mode, "dbg.map")) {
    +	} else if (!strcmp(region, "dbg.map")) {
     		return rz_core_get_boundaries_current_debug_map(core, interval);
    -	} else if (!strcmp(use_mode, "io.map")) {
    +	} else if (!strcmp(region, "io.map")) {
     		return rz_core_get_boundaries_current_io_map(core, interval);
    -	} else if (!strcmp(use_mode, "range") || !strcmp(use_mode, "io.maps")) {
    +	} else if (!strcmp(region, "range") || !strcmp(region, "io.maps")) {
     		return rz_core_get_boundaries_all_io_maps(core, interval);
    -	} else if (!strcmp(use_mode, "io.sky")) {
    +	} else if (!strcmp(region, "io.sky")) {
     		return rz_core_get_boundaries_all_io_skyline(core, interval);
    -	} else if (!strcmp(use_mode, "code")) {
    +	} else if (!strcmp(region, "code")) {
     		return rz_core_get_boundaries_code_only(core, interval);
    -	} else if (!strcmp(use_mode, "bin.segment")) {
    +	} else if (!strcmp(region, "bin.segment")) {
     		return rz_core_get_boundaries_current_bin_segment(core, interval);
    -	} else if (!strcmp(use_mode, "bin.section")) {
    +	} else if (!strcmp(region, "bin.section")) {
     		return rz_core_get_boundaries_current_bin_section(core, interval);
    -	} else if (!strcmp(use_mode, "bin.segments")) {
    +	} else if (!strcmp(region, "bin.segments")) {
     		return rz_core_get_boundaries_all_bin_segments(core, interval);
    -	} else if (!strcmp(use_mode, "bin.sections")) {
    +	} else if (!strcmp(region, "bin.sections")) {
     		return rz_core_get_boundaries_all_bin_sections(core, interval);
    -	} else if (!strcmp(use_mode, "analysis.fcn")) {
    +	} else if (!strcmp(region, "analysis.fcn")) {
     		return rz_core_get_boundaries_current_function(core, interval);
    -	} else if (!strcmp(use_mode, "analysis.bb")) {
    +	} else if (!strcmp(region, "analysis.bb")) {
     		return rz_core_get_boundaries_current_function_bb(core, interval);
    -	} else if (!strcmp(use_mode, "dbg.maps")) {
    +	} else if (!strcmp(region, "dbg.maps")) {
     		return rz_core_get_boundaries_all_debug_maps(core, interval);
    -	} else if (!strcmp(use_mode, "dbg.heap")) {
    +	} else if (!strcmp(region, "dbg.heap")) {
     		return rz_core_get_boundaries_debug_heap(core, interval);
    -	} else if (!strcmp(use_mode, "dbg.stack")) {
    +	} else if (!strcmp(region, "dbg.stack")) {
     		return rz_core_get_boundaries_debug_stack(core, interval);
    -	} else if (!strcmp(use_mode, "dbg.program")) {
    +	} else if (!strcmp(region, "dbg.program")) {
     		return rz_core_get_boundaries_debug_program(core, interval);
    -	} else if (rz_str_startswith(use_mode, "dbg.maps.")) {
    +	} else if (rz_str_startswith(region, "dbg.maps.")) {
     #define PARSE_PERMS(input, mode_name) rz_str_rwx(input + strlen(mode_name))
    -		int perms = PARSE_PERMS(use_mode, "dbg.maps.");
    +		int perms = PARSE_PERMS(region, "dbg.maps.");
     		return rz_core_get_boundaries_debug_maps(core, interval, perms, perms, false);
    -	} else if (rz_str_startswith(use_mode, "io.sky.")) {
    -		int perms = PARSE_PERMS(use_mode, "io.sky.");
    +	} else if (rz_str_startswith(region, "io.sky.")) {
    +		int perms = PARSE_PERMS(region, "io.sky.");
     		return rz_core_get_boundaries_io_skyline(core, interval, perms, perms);
    -	} else if (rz_str_startswith(use_mode, "io.maps.")) {
    -		int perms = PARSE_PERMS(use_mode, "io.maps.");
    +	} else if (rz_str_startswith(region, "io.maps.")) {
    +		int perms = PARSE_PERMS(region, "io.maps.");
     		return rz_core_get_boundaries_io_maps(core, interval, perms, perms);
    -	} else if (rz_str_startswith(use_mode, "bin.segments.")) {
    -		int perms = PARSE_PERMS(use_mode, "bin.segments.");
    +	} else if (rz_str_startswith(region, "bin.segments.")) {
    +		int perms = PARSE_PERMS(region, "bin.segments.");
     		return rz_core_get_boundaries_bin_segments(core, interval, perms, perms);
    -	} else if (rz_str_startswith(use_mode, "bin.sections.")) {
    -		int perms = PARSE_PERMS(use_mode, "bin.sections.");
    +	} else if (rz_str_startswith(region, "bin.sections.")) {
    +		int perms = PARSE_PERMS(region, "bin.sections.");
     		return rz_core_get_boundaries_bin_sections(core, interval, perms, perms);
     #undef PARSE_PERMS
     	}
     
    -	RZ_LOG_ERROR("core: unknown mode '%s' for %s\n", use_mode, in_key);
    +	RZ_LOG_ERROR("core: Invalid mode '%s' for [0x%" PFMT64x ", 0x%" PFMT64x "]\n",
    +		region, interval.addr, interval.size);
     	return NULL;
     }
    +
    +/**
    + * \brief      Returns a list of boundaries (as RzIOMap), based on the selected mode; see [search/analysis/zoom/[in/from/to]] for available modes.
    + *
    + * \param      core      The RzCore to use
    + * \param      from_key  The [search|analysis|zoom].from keyword to use in RzConfig
    + * \param      to_key    The [search|analysis|zoom].to keyword to use in RzConfig
    + * \param      in_key    The [search|analysis|zoom].in keyword to use in RzConfig
    + *
    + * \return     On success a valid pointer (can be an empty list), otherwise NULL
    + */
    +RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_get_boundaries_select(RZ_NONNULL RzCore *core, RZ_NONNULL const char *from_key, RZ_NONNULL const char *to_key, RZ_NONNULL const char *in_key) {
    +	rz_return_val_if_fail(core && from_key && to_key && in_key, NULL);
    +
    +	RzInterval interval;
    +	ut64 from = rz_config_get_i(core->config, from_key);
    +	ut64 to = rz_config_get_i(core->config, to_key);
    +	const char *use_mode = rz_config_get(core->config, in_key);
    +
    +	interval.addr = from;
    +	interval.size = to - from;
    +	return rz_core_get_boundaries(core, interval, use_mode);
    +}
    
  • librz/core/cconfig.c+10 0 modified
    @@ -2566,7 +2566,17 @@ static ConfigOptDescr search_in_opts[] = {
     
     static bool cb_search_in(void *user, void *data) {
     	RzConfigNode *node = (RzConfigNode *)data;
    +	RzCore *core = (RzCore *)user;
    +	RzInterval itv = {
    +		.addr = rz_config_get_i(core->config, "search.from"),
    +		.size = rz_config_get_i(core->config, "search.to")
    +	};
     	if (node->value[0] != '?') {
    +		RzList *bounds = rz_core_get_boundaries(core, itv, node->value);
    +		if (!bounds) {
    +			return false;
    +		}
    +		rz_list_free(bounds);
     		return true;
     	} else if (strlen(node->value) > 1 && node->value[1] == '?') {
     		rz_cons_printf("Valid values for search.in (depends on .from/.to and io.va):\n");
    
  • librz/core/cmd/cmd_search.c+5 6 modified
    @@ -1228,6 +1228,7 @@ static void __core_cmd_search_asm_infinite(RzCore *core, const char *arg) {
     		}
     		free(buf);
     	}
    +	rz_list_free(boundaries);
     }
     
     static void __core_cmd_search_asm_byteswap(RzCore *core, int nth) {
    @@ -1700,6 +1701,10 @@ static RzSearchOpt *setup_search_options(RzCore *core) {
     }
     
     static RzCmdStatus byte_pattern_search(RzCore *core, RZ_OWN RzSearchBytesPattern *pattern, RzCmdStateOutput *state) {
    +	if (!pattern) {
    +		RZ_LOG_ERROR("Failed to parse given pattern.\n");
    +		return RZ_CMD_STATUS_ERROR;
    +	}
     	RzSearchOpt *search_opts = setup_search_options(core);
     	RzList *hits = NULL;
     	if (!search_opts) {
    @@ -1709,11 +1714,6 @@ static RzCmdStatus byte_pattern_search(RzCore *core, RZ_OWN RzSearchBytesPattern
     
     	CMD_SEARCH_BEGIN();
     
    -	if (!pattern) {
    -		RZ_LOG_ERROR("Failed to parse given pattern.\n");
    -		goto error;
    -	}
    -
     	bool progress = rz_search_opt_get_show_progress(search_opts) != RZ_SEARCH_PROGRESS_DISABLED;
     	if (!rz_search_opt_set_cancel_cb(search_opts, cmd_search_progress_cancel, progress ? state : NULL)) {
     		RZ_LOG_ERROR("code: Failed to setup default search options.\n");
    @@ -1731,7 +1731,6 @@ static RzCmdStatus byte_pattern_search(RzCore *core, RZ_OWN RzSearchBytesPattern
     	return cmd_core_handle_search_hits(core, state, hits);
     
     error:
    -	rz_search_bytes_pattern_free(pattern);
     	rz_list_free(hits);
     	rz_search_opt_free(search_opts);
     	CMD_SEARCH_END();
    
  • librz/include/rz_core.h+1 0 modified
    @@ -1204,6 +1204,7 @@ RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_get_boundaries_debug_heap(RZ_NONNU
     RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_get_boundaries_debug_stack(RZ_NONNULL RzCore *core, const RzInterval interval);
     RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_get_boundaries_debug_program(RZ_NONNULL RzCore *core, const RzInterval interval);
     RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_get_boundaries_select(RZ_NONNULL RzCore *core, RZ_NONNULL const char *from_key, RZ_NONNULL const char *to_key, RZ_NONNULL const char *in_key);
    +RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_get_boundaries(RZ_NONNULL RzCore *core, RzInterval interval, const char *region);
     
     RZ_API bool rz_core_hack(RzCore *core, const char *op);
     RZ_API bool rz_core_dump(RzCore *core, const char *file, ut64 addr, ut64 size, int append);
    
  • librz/main/rizin.c+1 1 modified
    @@ -1003,8 +1003,8 @@ RZ_API int rz_main_rizin(int argc, const char **argv) {
     			if (asmbits) {
     				rz_config_set(r->config, "asm.bits", asmbits);
     			}
    -			rz_config_set(r->config, "search.in", "dbg.map"); // implicit?
     			rz_config_set(r->config, "cfg.debug", "true");
    +			rz_config_set(r->config, "search.in", "dbg.map");
     			perms = RZ_PERM_RWX;
     			if (opt.ind >= argc) {
     				RZ_LOG_ERROR("No program given to -d\n");
    
  • test/db/cmd/cmd_search_x+12 0 modified
    @@ -458,3 +458,15 @@ EXPECT=<<EOF
     0x00000076
     EOF
     RUN
    +
    +NAME=Heap use after free
    +FILE==
    +CMDS=<<EOF
    +e search.in=section
    +/x deadbeef
    +EOF
    +EXPECT=<<EOF
    +EOF
    +EXPECT_ERR=<<EOF
    +ERROR: core: Invalid mode 'section' for [0x0, 0xffffffffffffffff]
    +EOF
    

Vulnerability mechanics

Root cause

"Wrong pointer ownership in `byte_pattern_search()` causes a double free when an invalid `search.in` value leads to a NULL return from boundary resolution."

Attack vector

An attacker who can control the `search.in` configuration value (e.g., by setting it to an invalid string like `"section"`) triggers a code path where `rz_core_get_boundaries_select()` returns `NULL`. In the old code, the caller `byte_pattern_search()` would then jump to the `error` label, which called `rz_search_bytes_pattern_free(pattern)` — but the `pattern` pointer had already been passed as `RZ_OWN` to the function, meaning ownership was already transferred and the caller should not free it again. This results in a double free [CWE-415]. The attack requires local access to set configuration values and run a search command.

Affected code

The double free occurs in `librz/core/cmd/cmd_search.c` in the `byte_pattern_search()` function due to wrong pointer ownership. The patch also modifies `librz/core/cbounds.c` to refactor `rz_core_get_boundaries_select()` into a new `rz_core_get_boundaries()` function, and adds validation in `librz/core/cconfig.c` via the `cb_search_in` callback to reject invalid `search.in` values.

What the fix does

The patch moves the `NULL` check for `pattern` to the top of `byte_pattern_search()` and returns early with `RZ_CMD_STATUS_ERROR` instead of falling through to the `error` label. This eliminates the erroneous `rz_search_bytes_pattern_free(pattern)` call in the error path, fixing the double free. Additionally, the `cb_search_in` callback in `cconfig.c` now validates the `search.in` value by calling `rz_core_get_boundaries()` and rejecting invalid modes, preventing the NULL-return scenario that led to the double free. The refactored `rz_core_get_boundaries()` function centralizes boundary resolution.

Preconditions

  • configAttacker must be able to set the `search.in` configuration variable to an invalid value (e.g., `section`).
  • inputAttacker must trigger a byte pattern search command (e.g., `/x deadbeef`).
  • authAttacker requires local access to the Rizin command-line interface.

Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.