Skip to content

fix(vm): bound value stack accesses during module verification and execution - #171

Open
dmitry123 wants to merge 2 commits into
develfrom
claude/module-verification-bounds-2eb26f
Open

fix(vm): bound value stack accesses during module verification and execution#171
dmitry123 wants to merge 2 commits into
develfrom
claude/module-verification-bounds-2eb26f

Conversation

@dmitry123

@dmitry123 dmitry123 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Fixes FLU-1094.

A module accepted by RwasmModule::new_verified could read and write outside the value stack: verification only rejected a local depth of 0 and never checked the pops of Drop, BulkDrop, Select, or any arithmetic opcode. The interpreter had no release-mode guard either, because ValueStackPtr::inc_by/dec_by and ValueStack::get_release_unchecked_mut justified their unsafe with "all rwasm bytecode has been validated during translation" — true for bytecode this crate produces, false for the new_verified path, which exists precisely to accept foreign bytecode. LocalSet/LocalTee write through the same pointer, so this was an arbitrary write relative to a SmallVec whose inline storage lives in the caller's native stack frame.

Both halves of the suggested fix are implemented, with the runtime as the primary guarantee and verification as defense in depth.

Runtime bounds (src/vm/value_stack.rs)

ValueStackPtr now carries end alongside src and bounds-checks every pointer move and every dereference in all build profiles. An out-of-range access performs no memory access at all: the pointer is parked on the stack base and a sticky flag is raised, which RwasmExecutor::step turns into TrapCode::StackOverflow before the next instruction runs. The flag survives the executor re-deriving sp, because it travels into ValueStack through sync_stack_ptr and back out through stack_ptr.

This is what actually closes the hole, and it holds for any module regardless of how it was decoded. ValueStack::drop, push, pop, and peek_as_slice_mut were hardened the same way, and get_release_unchecked_mut is gone.

Rather than threading Result through ~150 call sites, the flag approach keeps the diff reviewable and costs one predictable compare-and-branch per stack op.

Verification (src/module/verification.rs)

verify_module now runs an abstract interpretation over the whole code section. Every function entry — source_pc, pc 0, and every CallInternal/ReturnCallInternal/RefFunc/elem_section target — seeds an emulated stack height of zero, which is propagated along all control flow edges over a Unvisited/Known/Unknown lattice, so each program counter is processed at most twice and the pass stays O(code_len).

New rejections:

  • local depths that reach past the addressable window, not just depth == 0
  • pops that drive the stack pointer below the window (Drop, BulkDrop, Select, arithmetic, …)
  • pushes past the top of the window (BulkConst, constants, …)
  • code that can run off the end of the code section, which is an out-of-bounds instruction read

Two honest limitations, both documented in the code:

  1. rWasm records no function signatures, so the height becomes Unknown after any call and only height-independent bounds apply from there. Threading the import linker into verify() would be needed to do better.
  2. The window is max(N_MAX_STACK_SIZE, largest StackCheck reservation in the module) rather than a tight per-function bound. A callee legitimately reads its parameters from below its own entry stack pointer, and rWasm does not record parameter counts, so a tighter bound is not derivable from the module alone.

Neither limitation is load-bearing for memory safety — the runtime check is.

new_checked/new_checked_exact rustdoc now says explicitly that they validate the encoding and perform no structural validation.

Testing

  • tests/value-stack-bounds.rs: the issue's repro (SIGSEGV on cargo test --release before this change) plus the LocalSet/LocalTee write variants, underflow, and overflow — each asserted to be both rejected by new_verified and trapped by the interpreter.
  • The same file compiles the three real fixtures in tests/assets and asserts they verify.
  • 9 new unit tests in verification.rs, including two that pin down what must not be rejected: locals addressed below the function entry, and bulk operands covered by a StackCheck.
  • To hunt for false rejections at scale I temporarily made RwasmModule::compile assert verify() on its own output and ran the full suite: the 92 Wasm spec testsuites, snippets, fluentbase, and the release nitro-verifier test all pass. That caught one real false positive during development — the compiler legitimately emits BulkConst(65534) for functions with many locals — which is why the window is module-derived rather than a fixed constant.

Full make test equivalent is green, cargo clippy --all-targets --all-features is clean.

Benchmark

benches/bench.rs, Comparisons/bench_strategy_rwasm:

time
devel 4.6994 – 4.7485 µs
this PR 4.5440 – 4.5812 µs

No regression; the small improvement is within codegen noise.

Summary by CodeRabbit

  • Bug Fixes

    • Improved runtime protection against value-stack underflow, overflow, and invalid accesses.
    • Malformed or unsafe bytecode now produces clear verification errors instead of causing invalid execution.
    • Added detection for code that falls through unexpectedly.
    • Execution now safely traps on stack overflow and resets state for reliable recovery.
  • Documentation

    • Clarified that module decoding validates binary encoding but does not verify bytecode structure.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@dmitry123, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 67e5a714-62d0-4445-bd86-c691925badbd

📥 Commits

Reviewing files that changed from the base of the PR and between 1b6d76e and fd8e437.

📒 Files selected for processing (1)
  • tests/value-stack-bounds.rs
📝 Walkthrough

Walkthrough

The PR adds static stack-height verification, bounds-checked value-stack operations, runtime StackOverflow detection, verification error variants, and regression tests. It also documents the scope of module decoding checks.

Changes

Stack safety

Layer / File(s) Summary
Static stack verification
src/module/verification.rs, src/module/mod.rs
The verifier tracks abstract stack heights, validates local and operand-stack bounds, detects code-section fallthrough, and documents decoding versus structural verification.
Bounds-checked value stack
src/vm/value_stack.rs
ValueStack and ValueStackPtr track capacity and sticky out-of-bounds state. Invalid stack reads, writes, drops, peeks, and pointer movement no longer use unchecked access.
Execution traps and regression coverage
src/vm/executor.rs, tests/value-stack-bounds.rs
The executor returns StackOverflow after invalid stack operations and resets execution state. Tests cover malformed accesses, stack limits, bulk operations, and compiled-module verification.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: hedwig0x

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes to value stack bounds during verification and execution.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/module-verification-bounds-2eb26f

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Criterion results (vs baseline)


running 83 tests
test compiler::compiled_expr::tests::compiledexpr_eval_const_returns_none_for_global_or_funcref ... ignored
test compiler::compiled_expr::tests::compiledexpr_from_const_roundtrips ... ignored
test compiler::compiled_expr::tests::compiledexpr_funcref_and_global_introspection ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_global_get_uses_context ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_add_mixed_const_and_global ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_add_mixed_global_and_funcref ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_add_wraps ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_const ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_sub_wraps ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i64_const ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i64_mul_wraps ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_ref_func_uses_context ... ignored
test compiler::compiled_expr::tests::compiledexpr_zero_is_zero ... ignored
test compiler::compiled_expr::tests::constop_eval_returns_value ... ignored
test compiler::compiled_expr::tests::empty_eval_context_always_none ... ignored
test compiler::compiled_expr::tests::eval_with_context_reads_globals_and_funcs ... ignored
test compiler::compiled_expr::tests::expr_op_combines_operands_and_propagates_none ... ignored
test compiler::compiled_expr::tests::funcrefop_reads_from_context ... ignored
test compiler::compiled_expr::tests::globalop_maps_value_kinds_correctly ... ignored
test compiler::compiled_expr::tests::op_clone_panics_for_expr_variant - should panic ... ignored
test compiler::compiled_expr::tests::op_clone_works_for_non_expr_variants ... ignored
test compiler::compiled_expr::tests::op_constant_encodes_f32_f64_bits ... ignored
test compiler::compiled_expr::tests::op_constant_encodes_funcref_externref_ids ... ignored
test compiler::compiled_expr::tests::op_constant_encodes_i32_i64 ... ignored
test compiler::drop_keep::tests::test_drop_keep_translation ... ignored
test compiler::func_type_registry::tests::deduplicates_matching_signatures ... ignored
test compiler::func_type_registry::tests::index_lookup_is_stable ... ignored
test compiler::func_type_registry::tests::resolves_unique_signatures_correctly ... ignored
test compiler::parser::tests::unsupported_component_model_returns_error ... ignored
test module::tests::test_decode_exact_rejects_trailing_garbage ... ignored
test module::tests::test_decode_module_wo_source_pc ... ignored
test module::tests::test_decode_rejects_partial_source_pc ... ignored
test module::tests::test_endianness ... ignored
test module::tests::test_module_encoding ... ignored
test module::verification::tests::accepts_bulk_operands_covered_by_a_stack_reservation ... ignored
test module::verification::tests::accepts_locals_addressed_below_the_function_entry ... ignored
test module::verification::tests::accepts_verified_encoded_module ... ignored
test module::verification::tests::regular_construction_does_not_verify ... ignored
test module::verification::tests::regular_decode_does_not_verify ... ignored
test module::verification::tests::rejects_branch_target_outside_code_section ... ignored
test module::verification::tests::rejects_bulk_operands_beyond_the_value_stack ... ignored
test module::verification::tests::rejects_call_target_outside_code_section ... ignored
test module::verification::tests::rejects_code_running_past_the_code_section ... ignored
test module::verification::tests::rejects_local_depth_beyond_the_value_stack ... ignored
test module::verification::tests::rejects_local_depth_beyond_the_value_stack_after_a_host_call ... ignored
test module::verification::tests::rejects_missing_table_index_payload ... ignored
test module::verification::tests::rejects_popping_below_the_value_stack ... ignored
test module::verification::tests::rejects_pushing_beyond_the_value_stack ... ignored
test module::verification::tests::rejects_section_index_outside_limits ... ignored
test module::verification::tests::rejects_source_pc_outside_code_section ... ignored
test module::verification::tests::rejects_zero_local_depth ... ignored
test strategy::types::tests::checked_memory_range_end_rejects_overflow ... ignored
test types::nan_preserving_float::tests::test_neg_nan_f32 ... ignored
test types::nan_preserving_float::tests::test_neg_nan_f64 ... ignored
test types::nan_preserving_float::tests::test_ops_f32 ... ignored
test types::nan_preserving_float::tests::test_ops_f64 ... ignored
test types::opcode::tests::test_fpu_opcode_encoding_uses_offset ... ignored
test types::opcode::tests::test_opcode_code_values ... ignored
test types::opcode::tests::test_opcode_encoding ... ignored
test types::opcode::tests::test_opcode_encoding_uses_explicit_code ... ignored
test types::opcode::tests::test_opcode_size ... ignored
test types::units::tests::bytes_new16 ... ignored
test types::units::tests::bytes_new32 ... ignored
test types::units::tests::bytes_new64 ... ignored
test types::units::tests::pages_checked_add ... ignored
test types::units::tests::pages_checked_sub ... ignored
test types::units::tests::pages_max ... ignored
test types::units::tests::pages_new ... ignored
test types::units::tests::pages_to_bytes ... ignored
test types::value::copysign_regression_works ... ignored
test types::value::wasm_float_max_regression_works ... ignored
test types::value::wasm_float_min_regression_works ... ignored
test vm::store::tests::clamps_runtime_memory_limit_to_global_maximum ... ignored
test wasmtime::tests::test_call_with_charging_linear_wasmtime ... ignored
test wasmtime::tests::test_call_with_charging_param_overflow_wasmtime ... ignored
test wasmtime::tests::test_call_with_charging_quadratic_wasmtime ... ignored
test wasmtime::tests::test_wasmtime_caller_memory_read_into_vec_checks_bounds_before_allocating ... ignored
test wasmtime::tests::test_wasmtime_caller_missing_memory_returns_trap ... ignored
test wasmtime::tests::test_wasmtime_executor_memory_read_into_vec_checks_bounds_before_allocating ... ignored
test wasmtime::tests::test_wasmtime_executor_missing_entrypoint_returns_trap ... ignored
test wasmtime::tests::test_wasmtime_snapshot_missing_memory_returns_trap ... ignored
test wasmtime::types::tests::maps_unknown_wasmtime_error_to_illegal_opcode ... ignored
test wasmtime::types::tests::maps_wasmtime_traps_to_rwasm_traps ... ignored

test result: ok. 0 passed; 0 failed; 83 ignored; 0 measured; 0 filtered out; finished in 0.00s

Criterion.rs ERROR: error: Failed to access file "/_work/rwasm/rwasm/target/criterion/Comparisons/bench_native/base/sample.json": No such file or directory (os error 2)
Comparisons/bench_native
                        time:   [4.7803 ns 4.7857 ns 4.7916 ns]
Found 69 outliers among 1000 measurements (6.90%)
  32 (3.20%) high mild
  37 (3.70%) high severe
Criterion.rs ERROR: error: Failed to access file "/_work/rwasm/rwasm/target/criterion/Comparisons/bench_strategy_wasmtime/base/sample.json": No such file or directory (os error 2)
Comparisons/bench_strategy_wasmtime
                        time:   [9.2283 µs 9.2433 µs 9.2592 µs]
Found 71 outliers among 1000 measurements (7.10%)
  30 (3.00%) high mild
  41 (4.10%) high severe
Criterion.rs ERROR: error: Failed to access file "/_work/rwasm/rwasm/target/criterion/Comparisons/bench_strategy_rwasm/base/sample.json": No such file or directory (os error 2)
Comparisons/bench_strategy_rwasm
                        time:   [8.4672 µs 8.4991 µs 8.5331 µs]
Found 103 outliers among 1000 measurements (10.30%)
  49 (4.90%) high mild
  54 (5.40%) high severe

Heads-up: runner perf is noisy; treat deltas as a smoke check.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
tests/value-stack-bounds.rs (2)

110-149: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Add execution coverage for the compiled fixtures.

This test proves that the new verifier does not reject bytecode this crate produces. It does not prove that the new runtime bounds checks leave those modules alone. A false positive in ValueStackPtr would turn a working module into a TrapCode::StackOverflow, and this test would still pass.

The three fixtures are named *-stack-ub.wasm, which suggests they previously exercised the out-of-bounds path. Run at least one of them through ExecutionEngine::execute and assert the result is not Err(TrapCode::StackOverflow).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/value-stack-bounds.rs` around lines 110 - 149, Extend
compiled_modules_pass_verification to execute at least one compiled stack-UB
fixture through ExecutionEngine::execute, using the existing compilation setup
and appropriate entrypoint inputs. Assert execution does not return
Err(TrapCode::StackOverflow), while preserving the current verification
assertions for all fixtures.

38-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the specific verification error.

assert!(verify(code.clone()).is_err()) passes for any verification failure. If the verifier later rejects these modules for an unrelated reason, for example a code-fallthrough regression, the test still passes and the local-depth coverage is silently lost.

Match on RwasmModuleVerificationError::LocalDepthOutOfBounds for the three local tests and on StackUnderflow for bulk_drop_below_the_stack_base_traps. The unit tests in src/module/verification.rs already use this stricter form.

Also applies to: 50-50, 63-63, 84-84

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/value-stack-bounds.rs` at line 38, Replace the broad is_err assertions
in the three local-depth tests and bulk_drop_below_the_stack_base_traps with
specific matches against RwasmModuleVerificationError::LocalDepthOutOfBounds and
StackUnderflow respectively, following the stricter pattern used in
src/module/verification.rs.
src/vm/executor.rs (1)

126-131: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the reset sequence into a helper.

Lines 108-111, lines 126-131, and lines 159-161 in run_with_stack_check repeat the same three statements: reset the value stack, reset the call stack, clear store.last_signature. A future addition to the dirty-state set has to be applied in three places.

♻️ Proposed helper
+    /// Clears the execution state left behind by a halted or trapped run.
+    fn reset_execution_state(&mut self) {
+        self.value_stack.reset();
+        self.call_stack.reset();
+        self.store.last_signature = None;
+    }

Then apply it at each site:

         if self.sp.is_out_of_bounds() {
-            self.value_stack.reset();
-            self.call_stack.reset();
-            self.store.last_signature = None;
+            self.reset_execution_state();
             return Err(TrapCode::StackOverflow);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vm/executor.rs` around lines 126 - 131, Extract the repeated value-stack
reset, call-stack reset, and store.last_signature clearing from
run_with_stack_check into a dedicated helper method on the executor. Replace the
reset sequences at the sites around lines 108-111, 126-131, and 159-161 with
calls to that helper, preserving each existing return or error path.
src/module/verification.rs (1)

350-417: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Make stack_effect exhaustive over Opcode.

The catch-all arm at Line 415 assigns (1, 1) to every opcode that no earlier arm matches. A new opcode with a different stack effect then gets a silently wrong effect. That can reject valid bytecode or accept invalid bytecode without any compile-time signal.

List the remaining opcodes explicitly and remove the catch-all, or keep a catch-all that is unreachable!() only after every variant is covered. The _ if opcode.is_binary_instruction() guard can stay, because a guarded arm does not affect exhaustiveness checking of the listed variants.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/module/verification.rs` around lines 350 - 417, Make stack_effect
exhaustive by explicitly matching every remaining Opcode variant not covered by
the existing arms, preserving the intended (1, 1) effect for the unary integer
opcodes currently handled by the catch-all. Remove the catch-all, or replace it
with unreachable!() only after all variants are explicitly covered; retain the
guarded is_binary_instruction arm.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/value-stack-bounds.rs`:
- Around line 1-5: Correct the module-level documentation in the value-stack
bounds test file: remove the claim that every module is rejected by
RwasmModule::new_verified, and state that some cases pass verification but are
still executed to validate the interpreter’s runtime stack-safety checks.
Preserve the explanation that the tests cover out-of-bounds accesses through
unverified or runtime-only paths.
- Around line 100-108: Update the comment in
pushing_past_the_reserved_stack_window_traps to state that the final push
exceeds the reserved value-stack window, not the second push. Add a verification
assertion using the existing verification path to confirm the module is rejected
with StackOverflow, while preserving the current execute-time trap assertion.

---

Nitpick comments:
In `@src/module/verification.rs`:
- Around line 350-417: Make stack_effect exhaustive by explicitly matching every
remaining Opcode variant not covered by the existing arms, preserving the
intended (1, 1) effect for the unary integer opcodes currently handled by the
catch-all. Remove the catch-all, or replace it with unreachable!() only after
all variants are explicitly covered; retain the guarded is_binary_instruction
arm.

In `@src/vm/executor.rs`:
- Around line 126-131: Extract the repeated value-stack reset, call-stack reset,
and store.last_signature clearing from run_with_stack_check into a dedicated
helper method on the executor. Replace the reset sequences at the sites around
lines 108-111, 126-131, and 159-161 with calls to that helper, preserving each
existing return or error path.

In `@tests/value-stack-bounds.rs`:
- Around line 110-149: Extend compiled_modules_pass_verification to execute at
least one compiled stack-UB fixture through ExecutionEngine::execute, using the
existing compilation setup and appropriate entrypoint inputs. Assert execution
does not return Err(TrapCode::StackOverflow), while preserving the current
verification assertions for all fixtures.
- Line 38: Replace the broad is_err assertions in the three local-depth tests
and bulk_drop_below_the_stack_base_traps with specific matches against
RwasmModuleVerificationError::LocalDepthOutOfBounds and StackUnderflow
respectively, following the stricter pattern used in src/module/verification.rs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7113a309-e7ae-4e78-b367-0ad66015f223

📥 Commits

Reviewing files that changed from the base of the PR and between b8f6091 and 1b6d76e.

📒 Files selected for processing (5)
  • src/module/mod.rs
  • src/module/verification.rs
  • src/vm/executor.rs
  • src/vm/value_stack.rs
  • tests/value-stack-bounds.rs

Comment thread tests/value-stack-bounds.rs Outdated
Comment thread tests/value-stack-bounds.rs
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.38012% with 50 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/vm/value_stack.rs 69.81% 32 Missing ⚠️
src/module/verification.rs 93.72% 14 Missing ⚠️
src/vm/executor.rs 69.23% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant