Post

Visect

Tool to automatically find Bisect for a given Crash PoC in V8.

Visect

Motivation

Bisect finding in V8 is a debugging technique that helps developers and bug hunters efficiently identify the exact Git Commit that introduced a bug or performance regression. It works by automating a binary search through the commit history testing intermediate commits to determine whether the issue is present or not, thereby narrowing down the problematic change.

This method is much faster than manually checking each commit, especially in a large codebase like V8. Developers typically use git bisect to streamline the process. By systematically halving the search range, bisect finding quickly isolates the faulty commit, making it easier to understand and fix the root cause of the issue. In essence, it’s a powerful way to track down regressions with minimal effort.

The idea of pinpointing the exact commit where a developer inadvertently introduced a bug, potentially leading to a 0-day vulnerability fascinated me, so I dove deeper into how such investigations were conducted. However, the process quickly became tedious and time-consuming, especially due to the repetitive cycle of compiling V8 over and over again. Realizing the inefficiency, I decided there had to be a better way. That’s when I built an automated tool to streamline bisect finding—requiring only a crash proof-of-concept (PoC) and the faulty commit as inputs. And thus, Visect was born.

Implementation

The Tool operates in 2 modes -

Each method improves on the traditional method of Bisect Finding in V8. We’ll look into how each one is implemented.

This approach is helpful when dealing with a bug or crash that provides minimal insight into the underlying cause or the specific area of the codebase it impacts. This approach closely mirrors the traditional git bisect method, and for good reason, since it’s incredibly effective. The only catch? Compiling V8 from scratch takes an excruciatingly long time. Even with automation, the script could churn for hours before pinpointing the problematic commit.

That’s when I discovered v8-asan builds, a goldmine maintained by Google, offering pre-built V8 binaries for nearly every commit. I have noticed VRP reports by Top Chrome researchers use these builds for Bisect Analysis, like Issue 40063542 by @Kipreyyy. Convenient, right?

This was a game-changer for automation: instead of compiling V8 each time, we could just download the builds (a mere 100-200MB) and test them instantly. After running multiple tests, I found that bisecting now took mere minutes—and with a fast internet connection, sometimes even under a minute. No more waiting for sluggish compiles: just pure, efficient debugging.

While git bisect is powerful, it requires the analyst to manually specify both a BAD_COMMIT (where the crash occurs) and a GOOD_COMMIT (an older, stable version where the PoC works fine without issue). But what if we don’t know the good commit upfront? Manually testing random historical commits is tedious and time-consuming.

That’s where Visect steps in. Instead of relying on guesswork, the script automatically probes commits at a fixed distance from the BAD_COMMIT, checking each one until it finds a valid GOOD_COMMIT. This eliminates the trial-and-error hassle, letting the tool handle the heavy lifting—so researchers can focus on the actual debugging, not commit archaeology.

Query Commit Database

This method is applicable when you’ve already analyzed a crash PoC and narrowed down the problematic section of the source code. Knowing where the bug is makes bisecting much faster, since the real challenge now is just identifying which commit introduced those changes.

At first glance, git blame seems like the obvious solution—and it does work well—but it has a limitation: it doesn’t support direct source code searches. You must provide exact filenames and line numbers, which isn’t always practical. There are hacky workarounds (like combining git grep with git blame), but I wanted something cleaner and more efficient.

The Solution: Instant Source Code Search Across All Commits

Instead of wrestling with git blame, I built this approach:

  • A Local Commit Database – The script creates a db/ folder and extracts every commit (95,538+ commits = about 2GB) into individual files .
  • Blazing-Fast Search with ripgrep – Now, I can directly search the entire V8 history for specific code snippets, instantly pinpointing every commit that touched the relevant code.
  • Targeted Analysis – With all matching commits in hand, I can quickly verify when and how the bug was introduced, drastically speeding up root cause analysis.

This method isn’t just faster—it’s more precise, letting the analyst zero in on critical changes without manual digging. No more guessing line numbers or wrestling with git blame’s limitations.

I’ll be conducting Case Studies on both these methods to check it’s effectiveness, but first let’s look how these methods can be used through the Visect tool.

Usage

First git clone the repository and inside it, create a python virtual environment

1
2
python3 -m venv venv
source venv/bin/activate

Then install the requirements using this command

1
pip3 install ripgrepy python-dotenv requests termcolor tqdm

After which you may run the script using -

1
python3 app.py

If everything goes correctly, you’ll be greeted by this menu -

Desktop View

The script only needs one initial input: the path to your local V8 repository. It stores this in .env for future runs, so you won’t need to enter it again.

Before starting, it performs a routine check to sync your commit database with the latest V8 changes. For the best results, just run git pull in your V8 repo beforehand, this ensures the tool catches every recent commit. After the initialization process is complete, you should see this screen -

Desktop View

Now, the Tool is ready to use. In the next section we’ll test out our tool against real world scenarios.

Case Studies

Although Visect is nothing but a result of sheer automation, the real question is: how does it perform against actual bugs and crash PoCs? To find out, I’ll walk through two case studies (one for each method discussed earlier) to showcase their strengths and limitations.

Case Study - 1

We’ll start with the Query Commit Database Method, since it’s the most straightforward to demonstrate.

We’ll be looking into Issue 400086889 reported by Seunghyun Lee. The author had found a bug in Wasm introduced in a 3-day old commit marked as 44171ac91e6. By reading the report, it seemed the author had been already monitoring the commits and knew exactly which code was responsible for the bug, which made bisect finding straightforward.

So, for this excercise, I’ll assume that we already know the buggy part of the code, but dont have any idea in which commit it was introduced. Can our Commit DB help in this case? Let’s check.

I’ll be commenting the buggy code presented by the author in his report below, and we’ll see if our Commit DB returns any results -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/wasm/canonical-types.h;drc=44171ac91e6a61c22ea8256ea8804b3955e4b5db;l=342
    bool EqualValueType(CanonicalValueType type1,
                        CanonicalValueType type2) const {
      const bool indexed = type1.has_index();
      if (indexed != type2.has_index()) return false;
      if (indexed) {
        return EqualTypeIndex(type1.ref_index(), type2.ref_index());
      }
      return type1 == type2;
    }

// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/wasm/value-type.h;drc=30200e13ddec41e3ea341c49ed1dff1878c743bd;l=1009
  constexpr CanonicalTypeIndex ref_index() const {
    return CanonicalTypeIndex{raw_index()};
  }

// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/wasm/value-type.h;drc=30200e13ddec41e3ea341c49ed1dff1878c743bd;l=689
  constexpr TypeIndex raw_index() const {
    DCHECK(has_index());
    return TypeIndex(value_type_impl::PayloadField::decode(bit_field_));         // [!] 20 bits
  }

Let’s try the search term EqualValueType(CanonicalValueType type1 from the top snippet to check if we can match the bisect found by the author (44171ac91e6). After checking with our tool we get -

Desktop View

As we can see, we found a few commits where the search string appears. The commits denote possible changes in the function EqualValueType. Among them is the bisect commit denoting that our script had done it’s job.

I was wondering if our tool can narrow down the search to a single commit. In the report, he author mentions that the type confusion vulnerability stemmed from two critical changes:

  • Removal of a security-critical CHECK() during type canonicalization
  • Insufficient validation in the subsequent CanonicalEquality check

This oversight allowed type index truncation for values exceeding 20 bits (specifically at this line - return TypeIndex(value_type_impl::PayloadField::decode(bit_field_)); ), creating a potential type confusion scenario.

Does our tool detect this line in any commits? Let’s check.

Desktop View

Yes it does! It directly points to the bisect commit which had introduced the code. This makes the Query Commit DB useful for:

  • Quickly finding Changes - No more digging through history manually
  • Checking changes - See when and how a function was modified

Case Study - 2

We’ll now look into Automatic Bisect Search. This method will find out the Bisect without any prior information of where the bug exists in the codebase automatically without any user intervention.

To Test this, we’ll check out CVE-2025-2135 reported by @Kipreyyy and @eternalsakura13. In the report they have found the bisect to be b8d3f7d0cf6. Let’s see if our tool can find it out on it’s own!

For the Run, we’ll grab the poc.js attached, and the Bad Commit - (6cb5d344ed1) based on the Revision - 99019 mentioned in the report.

Desktop View

As shown above, after putting the relevant details in the Tool, it starts with checking if the BAD COMMIT is valid. d8 should return a error code other than 0 or 1 for it to be a Valid Crash.

Desktop View

We’ll be putting "None" as the GOOD COMMIT as we don’t know it. This will activate the speculation capability of Visect which will add a fixed distance to the offset (currently set to 1024), until a GOOD COMMIT is found, which doesn’t result in a crash. This can be seen above clearly. Since the distance between Bisect and BAD COMMIT for this Testcase is more than 1500 commits apart, in this case the Tool will speculate twice.

Desktop View

Once the BAD COMMIT and GOOD COMMIT are defined, the Bisect Process can begin. It follows the standard procedure followed by git bisect.

Desktop View

After a few passes, we can see that our Tool succeeded in finding the Bisect Commit!

In this way, just in about ~6 mins (based on my internet speed), Visect was able to found out the Commit which introduced the Bug. This commit can later analysed to find the Root Cause and other analyses.

If you wish to try out Visect, you can find it on my Github.

Currently only Linux (x86_64) is supported. Windows Support for Visect is underway.

Credits

Hey There! If you’ve come across any bugs or have ideas for improvements, feel free to reach out to me on X! If your suggestion proves helpful and gets implemented, I’ll gladly credit you in this dedicated Credits section. Thanks for reading!

This post is licensed under CC BY 4.0 by the author.

Trending Tags