<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://bharadwajraju.com/</id>
  <title>Bharadwaj Raju</title>
  <updated>2026-08-20T15:10:33.847750+00:00</updated>
  <author>
    <name>Bharadwaj Raju</name>
  </author>
  <link href="https://bharadwajraju.com/" rel="alternate"/>
  <link href="https://bharadwajraju.com/feed.xml"/>
  <generator uri="https://lkiesow.github.io/python-feedgen" version="1.0.0">python-feedgen</generator>
  <entry>
    <id>https://bharadwajraju.com/posts/btrfs-snapshots-in-kde</id>
    <title>Btrfs Snapshot Integration in KDE</title>
    <updated>2026-08-20T00:00:00+05:30</updated>
    <content type="CDATA"><![CDATA[
<p>I have been working on integrating Btrfs snapshots into <abbr>KDE</abbr> software. The central part of this work has
been realized in the form of <a href="https://invent.kde.org/system/kio-snapshot"><abbr>KIO</abbr> Snapshot</a>, which has just been released. Here I want to discuss what it is, how it works, how it was developed, and the surrounding work across <abbr>KDE</abbr>.</p>
<h2>Background</h2>

<p>Among the key features of Btrfs is the ability to take efficient <em>snapshots</em> of <span class="sidenote-ref"><span class="sidenote-ref-text">subvolumes</span><span class="sidenote-number"><aside class="sidenote" role="note"><!--&#8203;-->&nbsp;<span class="sidenote-surroundings">&nbsp;(</span>Subvolumes are independently manageable directory trees within your filesystem. You can snapshot or rollback a subvolume atomically and independently from the rest of your filesystem. Snapshots are just special cases of subvolumes.<span class="sidenote-surroundings">)&nbsp;</span></aside></span></span>. They are efficient because Btrfs makes snapshots share file extents with the originals, so snapshots only take up additional space where they differ from the original. Thus it is cheap to take snapshots frequently without worrying about disk space.</p>

<p>This can be used to build very handy universal “undo” or “time travel” functionality for the user. Yet, though there are graphical tools to work with Btrfs snapshots such as <a href="https://gitlab.com/btrfs-assistant/btrfs-assistant">Btrfs Assistant</a>, these are separate from normal file browsing, and are rather technical tools concerned with the orchestration of snapshots. Direct integration of snapshots into the file browser itself for mundane end-user purposes, like Windows has with Previous Versions or macOS with the famous Time Machine, has been lacking in the Linux world. The aim of <abbr>KIO</abbr> Snapshot is to build that kind of direct integration for <abbr>KDE</abbr>.</p>
<h2>What it is</h2>

<p><abbr>KIO</abbr> Snapshot lets Dolphin (indeed any <abbr>KDE</abbr> software) list and access
Btrfs snapshots of a file or subvolume.</p>

<p>You can right-click on a file and go to a folder view showing you all the distinct past versions of it as saved in your snapshots.            <small class="sidenote marginnote">&#8203;<span class="sidenote-surroundings">&nbsp;(</span>At first, I wrote the snapshots-for-file case as a dialog with buttons to open or restore (like Windows’ Previous Versions feature), but I changed it to be a full virtual folder, since that would be much more flexible — now a user could select multiple previous versions and open them in a comparison tool, or copy them somewhere, or check their metadata easily, or whatever else they wished.<span class="sidenote-surroundings">)&nbsp;</span></small></p>


<figure>
    
    <img class="" src="https://bharadwajraju.com/posts/btrfs-snapshots-in-kde/filesnapshots-dark.png" alt="Screenshot showing the filesnapshots KIO worker, listing the versions of a file" title="Screenshot showing the filesnapshots KIO worker, listing the versions of a file"
    
    
    />
    <figcaption></figcaption>
</figure>
<p>You also have views into entire directory trees of subvolumes at their various snapshots.</p>

<figure>
    
    <img class="" src="https://bharadwajraju.com/posts/btrfs-snapshots-in-kde/subvolume-snapshots-dark.png" alt="Screenshot showing the snapshot KIO worker, listing the snapshots of a subvolume" title="Screenshot showing the snapshot KIO worker, listing the snapshots of a subvolume"
    
    
    />
    <figcaption></figcaption>
</figure>


<p>Note that it does not <em>take</em> snapshots, it only allows access to them. To take snapshots, you
would have to do it manually, or through an orchestrator like <span class="sidenote-ref"><span class="sidenote-ref-text"><a href="https://snapper.io/">Snapper</a></span><span class="sidenote-number"><aside class="sidenote" role="note"><!--&#8203;-->&nbsp;<span class="sidenote-surroundings">&nbsp;(</span>If you <em>are</em> using Snapper, you should add yourself to the <code>ALLOW_USERS</code> setting for your Snapper config and turn on <code>SYNC_ACL=yes</code>, to allow rootless access to your snapshots.
See <a href="http://snapper.io/manpages/snapper-configs.html"><abbr>Snapper-Configs(5)</abbr></a> and <a href="http://snapper.io/manpages/snapper.html#permissions"><abbr>Snapper(8) § Permissions</abbr></a> for details.<span class="sidenote-surroundings">)&nbsp;</span></aside></span></span>.</p>

<h2>How it works</h2>
<p>Mainly it uses <a href="https://github.com/kdave/btrfs-progs/tree/master/libbtrfsutil">libbtrfsutil</a> from btrfs-progs to talk to the filesystem, and <abbr>KDE</abbr> Frameworks’ <a href="https://develop.kde.org/docs/features/solid/">Solid</a> to query filesystems and mounts on a more meta level.</p>

<p>Now, the Btrfs <abbr>API</abbr> is quite conservative in what it allows non-superusers to do with it. Even a question as seemingly innocuous as “what subvolume is this path in?” cannot be answered for a non-superuser directly. The consequence of this is that on my first
attempt at building this integration, I had one component running as a system-level DBus service which would let users query stuff like this for files they owned.</p>

<p>Luckily, a nudge from Méven Car made me realize that with some working-around, I could build out all the features without anything running as root. For example,
while Btrfs is loath to let you get the subvolume <abbr>ID</abbr> for a path or vice versa, it will happily give you a listing of the subvolumes (that you can access) under a path. From there you can derive all the information needed.</p>


<p>Using this, <abbr>KIO</abbr> Snapshot implements a <abbr>KIO</abbr> worker that provides the <code>snapshot://</code> protocol, which will be understood by all programs which use <abbr>KDE</abbr> Frameworks.
This protocol provides a virtual view into the snapshots in a filesystem along two dimensions: the snapshots for a given subvolume, each of which is a browsable directory tree in itself;
and the snapshots for a given file across all snapshots of its containing subvolume.</p>


<h2>Allied work</h2>
<p>Aside from <abbr>KIO</abbr> Snapshot itself, I also worked on some small things in other parts of <abbr>KDE</abbr> to support it.</p>
<ul>	<li>Fixed a bug in <abbr>KIO</abbr> which caused inconsistent behavior in Dolphin’s location bar: <a href="https://invent.kde.org/frameworks/kio/-/merge_requests/2305"><abbr>KIO</abbr> <abbr>MR</abbr> #2305</a></li>	<li>Fixed how Solid handled Btrfs layouts like the one used in <abbr>KDE</abbr> Linux: <a href="https://invent.kde.org/frameworks/solid/-/merge_requests/261">Solid <abbr>MR</abbr> #261</a></li>	<li>Special default view settings for <abbr>KIO</abbr> Snapshot’s views in Dolphin: <a href="https://invent.kde.org/system/dolphin/-/merge_requests/1347">Dolphin <abbr>MR</abbr> #1347</a></li>	<li>Experimented with adding support for Btrfs subvolumes into Solid itself — this is just a rough proof-of-concept, and maybe this work is more appropriate further upstream in UDisks — but here it is anyway: <a href="https://invent.kde.org/frameworks/solid/-/tree/work/bharadwaj/btrfs-subvolumes?ref_type=heads">Solid branch btrfs-subvolumes</a></li></ul><h2><abbr>KDE</abbr> Linux</h2>
<p><abbr>KDE</abbr> Linux will soon ship with Snapper and <abbr>KIO</abbr> Snapshot out-of-the-box.</p>

<p>Hadi Chokr did a lot of excellent integration work here: <a href="https://invent.kde.org/kde-linux/kde-linux/-/merge_requests/475">migrating the filesystem layout to make all user homes subvolumes</a>, and <a href="https://invent.kde.org/kde-linux/kde-linux/-/merge_requests/613">automatically integrating and configuring Snapper for all users</a> seamlessly.</p>

<p>This is part of a broader initiative in <abbr>KDE</abbr> Linux to <a href="https://invent.kde.org/kde-linux/kde-linux/-/work_items/666">improve data backup and restore systems</a>, which has also overseen improvements elsewhere, such as in the Kup backup system.</p>
<h2>Release</h2>
<p>The <a href="https://download.kde.org/stable/kio-snapshot/">first stable release</a> of it was made today (thanks to Bhushan Shah for helping with the release process). I imagine it should be getting packaged into distros fairly soon, thanks to the infrastructure that comes with being a <abbr>KDE</abbr> project, but even then it should be easy enough to compile from source. Please use it and <a href="https://bugs.kde.org/enter_bug.cgi?product=kio-snapshot&component=general">report bugs and requests</a>, thank you!</p>
]]></content>
    <link href="https://bharadwajraju.com/posts/btrfs-snapshots-in-kde"/>
    <published>2026-08-20T00:00:00+05:30</published>
  </entry>
  <entry>
    <id>https://bharadwajraju.com/posts/kernel-mentorship</id>
    <title>Being in the Linux Kernel Mentorship</title>
    <updated>2025-09-10T00:00:00+05:30</updated>
    <content type="CDATA"><![CDATA[
<p>From March to August of this year, I have been a mentee in the Linux Foundation’s <a href="https://wiki.linuxfoundation.org/lkmp">Linux Kernel Mentorship Program</a> alongside my university studies.</p>

I’ve always kind of idolized kernel development, so this was a great opportunity to see what it is really like.

<p>I stumbled upon it quite by accident. <span class="sidenote-ref"><span class="sidenote-ref-text">I was browsing LinkedIn</span><span class="sidenote-number"><aside class="sidenote" role="note"><!--&#8203;-->&nbsp;<span class="sidenote-surroundings">&nbsp;(</span>Not something I usually ever do, really.<span class="sidenote-surroundings">)&nbsp;</span></aside></span></span> looking at alumni from my university, when I saw that one of them, who was working as a kernel engineer at Google, had reposted an announcement for the <abbr>LKMP</abbr>. I clicked through and that was that.</p>
<h2>Application & Acceptance</h2>
<p>I had to create an account on the <abbr>LFX</abbr> Mentorship portal, fill out my profile and a small statement-of-purpose, and then I was given a list of tasks on the basis of which I would be accepted (or not). These tasks ranged from completing <a href="https://training.linuxfoundation.org/training/a-beginners-guide-to-linux-kernel-development-lfc103/">a beginner’s course in kernel development from <abbr>LFX</abbr></a>, to writing a cover letter, to building and experimenting with writing simple kernel modules, and even making patches for small mistakes we could find. I managed to get 5 minor patches into the kernel as part of these preliminary tasks.</p>

<p>I finished my tasks and spent around a week or so awaiting the result. On the 26th of February, I got my acceptance email.</p>
<h2>Starting to Work</h2>
<p>I was invited to a Discord server for the mentees, and each Wednesday we had online meetings with our mentor Shuah Khan, where we covered various useful tools, discussed contribution opportunities, and got non-public (and less harsh) reviews and feedback on our patch attempts.</p>

<p>I decided to focus on fixing bugs, and to that end I tried to fix bugs from <a href="https://syzkaller.appspot.com/">syzkaller</a>. It’s a hosted public dashboard for bugs found via <a href="https://github.com/google/syzkaller/blob/master/docs/syzbot.md">the syz fuzzing system</a>. These are mostly kernel warnings, and reports from <abbr>KASAN</abbr> (Kernel Address Sanitizer), <abbr>KMSAN</abbr> (Memory), and <abbr>UBSAN</abbr> (Undefined Behavior).</p>

<p>This was hard. Not all bugs had working reproducers, or were comprehensible to someone new to the subsystem — to say nothing of the false positives. Most of the viable ones would be solved by an actual kernel engineer faster than I could reproduce and investigate it.</p>

<p>Despite this, I managed to get some bugfixes in bcachefs, from syzkaller. I’ll try to recount the process of investigating and fixing each bug in detail, in the hopes that it might be useful to others looking to fix kernel bugs.</p>
<h2>Fixes in bcachefs</h2>
<p>I picked bcachefs mostly by chance; it happened to have several syzkaller reports when
I looked at the dashboard, and they seemed approachable.</p>
<h3>Atomic Contexts and Blocking Functions</h3>
<p class="unjustifiable">I started by picking this syzkaller bug: <a href="https://syzkaller.appspot.com/bug?extid=c82cd2906e2f192410bb">BUG: sleeping function called from invalid context in <code>bch2_printbuf_make_room</code> (2)</a>.</p>

<p>Let’s look at (the relevant parts of) the crash report.</p>
<pre><code>BUG: sleeping function called from invalid context at ./include/linux/sched/mm.h:321
in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid: 5828, name: syz-executor246
preempt_count: 0, expected: 0
RCU nest depth: 1, expected: 0
3 locks held by syz-executor246/5828:
 #0: ffff88807ad6a0e0 (&type->s_umount_key#42/1){+.+.}-{4:4}, at: alloc_super+0x221/0x9d0 fs/super.c:344
 #1: ffff888075a84210 (&c->btree_trans_barrier){.+.+}-{0:0}, at: srcu_lock_acquire include/linux/srcu.h:161 [inline]
 #1: ffff888075a84210 (&c->btree_trans_barrier){.+.+}-{0:0}, at: srcu_read_lock include/linux/srcu.h:253 [inline]
 #1: ffff888075a84210 (&c->btree_trans_barrier){.+.+}-{0:0}, at: __bch2_trans_get+0x7ed/0xd40 fs/bcachefs/btree_iter.c:3386
 #2: ffffffff8ed3b560 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:331 [inline]
 #2: ffffffff8ed3b560 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:841 [inline]
 #2: ffffffff8ed3b560 (rcu_read_lock){....}-{1:3}, at: bch2_bkey_pick_read_device+0x29c/0x19b0 fs/bcachefs/extents.c:144
CPU: 0 UID: 0 PID: 5828 Comm: syz-executor246 Not tainted 6.14.0-syzkaller-11270-g08733088b566 #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2025
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x241/0x360 lib/dump_stack.c:120
 __might_resched+0x558/0x6c0 kernel/sched/core.c:8818
 might_alloc include/linux/sched/mm.h:321 [inline]
 slab_pre_alloc_hook mm/slub.c:4089 [inline]
 slab_alloc_node mm/slub.c:4167 [inline]
 __do_kmalloc_node mm/slub.c:4317 [inline]
 __kmalloc_node_track_caller_noprof+0xd3/0x4d0 mm/slub.c:4337
 __do_krealloc mm/slub.c:4895 [inline]
 krealloc_noprof+0x10f/0x300 mm/slub.c:4948
 bch2_printbuf_make_room+0x1f1/0x350 fs/bcachefs/printbuf.c:59
 bch2_prt_printf+0x269/0x6d0 fs/bcachefs/printbuf.c:186
 bch2_log_msg_start fs/bcachefs/error.c:19 [inline]
 bch2_fs_trans_inconsistent fs/bcachefs/error.c:63 [inline]
 bch2_fs_inconsistent+0x143/0x220 fs/bcachefs/error.c:81
 bch2_dev_rcu fs/bcachefs/sb-members.h:226 [inline]
 bch2_bkey_pick_read_device+0x95e/0x19b0 fs/bcachefs/extents.c:165
 bch2_btree_node_read+0x7ac/0x29e0 fs/bcachefs/btree_io.c:1706
 __bch2_btree_root_read fs/bcachefs/btree_io.c:1796 [inline]
 bch2_btree_root_read+0x656/0x7e0 fs/bcachefs/btree_io.c:1818
 read_btree_roots+0x3d7/0xa80 fs/bcachefs/recovery.c:581
 bch2_fs_recovery+0x28e4/0x3e20 fs/bcachefs/recovery.c:928
 bch2_fs_start+0x2fb/0x610 fs/bcachefs/super.c:1060
 bch2_fs_get_tree+0x113e/0x18f0 fs/bcachefs/fs.c:2253
 vfs_get_tree+0x90/0x2b0 fs/super.c:1759
 do_new_mount+0x2cf/0xb70 fs/namespace.c:3878
 do_mount fs/namespace.c:4218 [inline]
 __do_sys_mount fs/namespace.c:4429 [inline]
 __se_sys_mount+0x38c/0x400 fs/namespace.c:4406
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xf3/0x230 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f</code></pre>
<p class="unjustifiable">Now, where do we look for the bug? The first thing to look for is the last bcachefs function in the call trace, because it is a reasonable assumption that the bug is in bcachefs code and not in core kernel memory management code. That is <code>bch2_printbuf_make_room</code>.</p>

<p>Right, but what is an “invalid context” anyway? <span class="sidenote-ref"><span class="sidenote-ref-text">Researching the error message</span><span class="sidenote-number"><aside class="sidenote" role="note"><!--&#8203;-->&nbsp;<span class="sidenote-surroundings">&nbsp;(</span><a href="https://stackoverflow.com/questions/16538824/bug-sleeping-function-called-from-invalid-context-at-mm-slub-c1719">Even good old StackOverflow suffices.</a><span class="sidenote-surroundings">)&nbsp;</span></aside></span></span> will tell us that “invalid” here is “atomic”. You can't call a function which may sleep from an atomic context.</p>

<p class="unjustifiable">Although we don’t need this to solve the bug, we might as well ask ourselves why we’re in an atomic context here. The reason can be seen in the <code>locks held</code> part of the report, which lists an <code>rcu_read_lock</code> held by <code>bch2_bkey_pick_read_device</code>. <span class="sidenote-ref"><span class="sidenote-ref-text">It is illegal to block while in an <abbr>RCU</abbr> read-side critical section.</span><span class="sidenote-number"><aside class="sidenote" role="note"><!--&#8203;-->&nbsp;<span class="sidenote-surroundings">&nbsp;(</span><a href="https://www.kernel.org/doc/html/next/RCU/whatisRCU.html#rcu-read-lock">https://www.kernel.org/doc/html/next/RCU/whatisRCU.html#rcu-read-lock</a><span class="sidenote-surroundings">)&nbsp;</span></aside></span></span></p>

<p class="unjustifiable">What are the offending functions? Let’s look at what <code>bch2_printbuf_make_room</code> is doing, exactly.</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/printbuf.c?id=08733088b566b58283f0f12fb73f5db6a9a9de30#n59">fs/bcachefs/printbuf.c</a></div><pre><code><span class=kt>int</span><span class=w> </span><span class=nf>bch2_printbuf_make_room</span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>printbuf</span><span class=w> </span><span class=o>*</span><span class=n>out</span><span class=p>,</span><span class=w> </span><span class=kt>unsigned</span><span class=w> </span><span class=n>extra</span><span class=p>)</span>
<span class=p>{</span>
<span class=w>	</span>…
<span class=w>  </span>
<span class=w>	</span><span class=cm>/*</span>
<span class=cm>	 * Note: output buffer must be freeable with kfree(), it's not required</span>
<span class=cm>	 * that the user use printbuf_exit().</span>
<span class=cm>	 */</span><hll><span class=w>	</span><span class=kt>char</span><span class=w> </span><span class=o>*</span><span class=n>buf</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>krealloc</span><span class=p>(</span><span class=n>out</span><span class=o>-></span><span class=n>buf</span><span class=p>,</span><span class=w> </span><span class=n>new_size</span><span class=p>,</span><span class=w> </span><span class=o>!</span><span class=n>out</span><span class=o>-></span><span class=n>atomic</span><span class=w> </span><span class=o>?</span><span class=w> </span><span class=n>GFP_KERNEL</span><span class=w> </span><span class=o>:</span><span class=w> </span><span class=n>GFP_NOWAIT</span><span class=p>);</span></hll>
<span class=w>	</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=o>!</span><span class=n>buf</span><span class=p>)</span><span class=w> </span><span class=p>{</span>
<span class=w>		</span><span class=n>out</span><span class=o>-></span><span class=n>allocation_failure</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=nb>true</span><span class=p>;</span>
<span class=w>		</span><span class=n>out</span><span class=o>-></span><span class=n>overflow</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=nb>true</span><span class=p>;</span>
<span class=w>		</span><span class=k>return</span><span class=w> </span><span class=o>-</span><span class=n>ENOMEM</span><span class=p>;</span>
<span class=w>	</span><span class=p>}</span>

<span class=w>	</span><span class=n>out</span><span class=o>-></span><span class=n>buf</span><span class=w>	</span><span class=o>=</span><span class=w> </span><span class=n>buf</span><span class=p>;</span>
<span class=w>	</span><span class=n>out</span><span class=o>-></span><span class=n>size</span><span class=w>	</span><span class=o>=</span><span class=w> </span><span class=n>new_size</span><span class=p>;</span>
<span class=w>	</span><span class=k>return</span><span class=w> </span><span class=mi>0</span><span class=p>;</span>
<span class=p>}</span>
</code></pre> </div>
<p class="unjustifiable">That <code>krealloc</code> call is what the crash report is complaining about as well. It’s interesting that the call does seem to think about atomicity, from the <code><span class="o">!</span><span class="n">out</span><span class="o">-&gt;</span><span class="n">atomic</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="n">GFP_KERNEL</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">GFP_NOWAIT</span></code>. Then, <code><span class="n">out</span><span class="o">-&gt;</span><span class="n">atomic</span></code> must not be correctly maintained? Let’s step back in the call trace and look at how the caller is using this <code><span class="k">struct</span><span class="w"> </span><span class="nc">printbuf</span></code>.</p>

<p class="unjustifiable">First we would look at <code>bch2_prt_printf</code>, but that’s not where the <code><span class="k">struct</span><span class="w"> </span><span class="nc">printbuf</span></code> is constructed. Neither is it one more step back, in <code>bch2_log_msg_start</code>. Finally we see it being constructed in <code>bch2_fs_trans_inconsistent</code>:</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/error.c?id=08733088b566b58283f0f12fb73f5db6a9a9de30#n61">fs/bcachefs/error.c</a></div><pre><code><span class=n>__printf</span><span class=p>(</span><span class=mi>3</span><span class=p>,</span><span class=w> </span><span class=mi>0</span><span class=p>)</span>
<span class=k>static</span><span class=w> </span><span class=kt>bool</span><span class=w> </span><span class=n>bch2_fs_trans_inconsistent</span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>bch_fs</span><span class=w> </span><span class=o>*</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=k>struct</span><span class=w> </span><span class=nc>btree_trans</span><span class=w> </span><span class=o>*</span><span class=n>trans</span><span class=p>,</span>
<span class=w>				       </span><span class=k>const</span><span class=w> </span><span class=kt>char</span><span class=w> </span><span class=o>*</span><span class=n>fmt</span><span class=p>,</span><span class=w> </span><span class=kt>va_list</span><span class=w> </span><span class=n>args</span><span class=p>)</span>
<span class=p>{</span><hll><span class=w>	</span><span class=k>struct</span><span class=w> </span><span class=nc>printbuf</span><span class=w> </span><span class=n>buf</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>PRINTBUF</span><span class=p>;</span></hll>
<span class=w>	</span><span class=n>bch2_log_msg_start</span><span class=p>(</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=o>&</span><span class=n>buf</span><span class=p>);</span>

<span class=w>	</span><span class=n>prt_vprintf</span><span class=p>(</span><span class=o>&</span><span class=n>buf</span><span class=p>,</span><span class=w> </span><span class=n>fmt</span><span class=p>,</span><span class=w> </span><span class=n>args</span><span class=p>);</span>
<span class=w>	</span><span class=n>prt_newline</span><span class=p>(</span><span class=o>&</span><span class=n>buf</span><span class=p>);</span>

<span class=w>	</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=n>trans</span><span class=p>)</span>
<span class=w>		</span><span class=n>bch2_trans_updates_to_text</span><span class=p>(</span><span class=o>&</span><span class=n>buf</span><span class=p>,</span><span class=w> </span><span class=n>trans</span><span class=p>);</span>
<span class=w>	</span><span class=kt>bool</span><span class=w> </span><span class=n>ret</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>__bch2_inconsistent_error</span><span class=p>(</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=o>&</span><span class=n>buf</span><span class=p>);</span>
<span class=w>	</span><span class=n>bch2_print_string_as_lines</span><span class=p>(</span><span class=n>KERN_ERR</span><span class=p>,</span><span class=w> </span><span class=n>buf</span><span class=p>.</span><span class=n>buf</span><span class=p>);</span>

<span class=w>	</span><span class=n>printbuf_exit</span><span class=p>(</span><span class=o>&</span><span class=n>buf</span><span class=p>);</span>
<span class=w>	</span><span class=k>return</span><span class=w> </span><span class=n>ret</span><span class=p>;</span>
<span class=p>}</span>
</code></pre> </div>
<p>and expanding the <code>PRINTBUF</code> macro…</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/printbuf.h?id=08733088b566b58283f0f12fb73f5db6a9a9de30#n133">fs/bcachefs/printbuf.c</a></div><pre><code><span class=cm>/* Initializer for a heap allocated printbuf: */</span>
<span class=cp>#define PRINTBUF ((struct printbuf) { .heap_allocated = true })</span>
</code></pre> </div>
<p>So <code><span class="n">buf</span><span class="p">.</span><span class="n">atomic</span></code> is <code><span class="mi">0</span></code>. Hence the <code>krealloc</code> call is made with <code>GFP_KERNEL</code>, which is potentially blocking. How do we correctly handle this <code><span class="p">.</span><span class="n">atomic</span></code> field? Let’s look for examples in <code>fs/bcachefs</code>.</p>
<pre><code>❯<span class=w> </span><span class=n>rg</span><span class=w> </span><span class=o>-</span><span class=n>F</span><span class=w> </span>'<span class=p>.</span><span class=n>atomic</span>'
<span class=n>journal</span><span class=p>.</span><span class=n>c</span>
<span class=mi>145</span><span class=o>:</span><span class=w>    </span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>
<span class=mi>273</span><span class=o>:</span><span class=w>            </span><span class=n>pbuf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>
<span class=mi>287</span><span class=o>:</span><span class=w>            </span><span class=n>err</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>

<span class=n>btree_io</span><span class=p>.</span><span class=n>c</span>
<span class=mi>2246</span><span class=o>:</span><span class=w>           </span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>

<span class=n>error</span><span class=p>.</span><span class=n>c</span>
<span class=mi>46</span><span class=o>:</span><span class=w>     </span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>
<span class=mi>62</span><span class=o>:</span><span class=w>     </span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>

<span class=n>debug</span><span class=p>.</span><span class=n>c</span>
<span class=mi>515</span><span class=o>:</span><span class=w>            </span><span class=n>i</span><span class=o>-></span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>
<span class=mi>531</span><span class=o>:</span><span class=w>            </span><span class=o>--</span><span class=n>i</span><span class=o>-></span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=p>;</span>

<span class=n>journal_reclaim</span><span class=p>.</span><span class=n>c</span>
<span class=mi>225</span><span class=o>:</span><span class=w>                    </span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>

<span class=n>alloc_foreground</span><span class=p>.</span><span class=n>c</span>
<span class=mi>1638</span><span class=o>:</span><span class=w>   </span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>
<span class=mi>1647</span><span class=o>:</span><span class=w>   </span><span class=o>--</span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=p>;</span>

<span class=n>btree_locking</span><span class=p>.</span><span class=n>c</span>
<span class=mi>164</span><span class=o>:</span><span class=w>            </span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>
<span class=mi>200</span><span class=o>:</span><span class=w>    </span><span class=n>buf</span><span class=p>.</span><span class=n>atomic</span><span class=o>++</span><span class=p>;</span>
</code></pre> 
<p>Alright. So we’re supposed to increment the <code><span class="p">.</span><span class="n">atomic</span></code> field before working with the buffer, and decrement it when done. Let’s do that. It should be fixed now, right?</p>

<p class="unjustifiable">Nope, we get a similar crash. What gives? Turns out there’s one more blocking function left in this codepath. It’s <code>bch2_print_string_as_lines</code>, due to it calling <code>console_lock</code>. Luckily for us, there is a direct replacement function already there, called… <code>bch2_print_string_as_lines_nonblocking</code>. We just replace the call, and we’re finally done.</p>
            <small class="sidenote marginnote">&#8203;<span class="sidenote-surroundings">&nbsp;(</span>Kent Overstreet is really nice, by the way. He even suggested possible further work in that thread for me. Namely, trying to replace <code>print_string_as_lines</code> entirely by calling something lower-level which didn't have the 1k chars limitation of <code>printk</code>. Unfortunately I did not find a straightforward lower function to <code>printk</code> that would’ve done what we wanted, but still.<span class="sidenote-surroundings">)&nbsp;</span></small>
<p class="unjustifiable">Now, having done this, I submitted <a href="https://lore.kernel.org/all/20250402161043.161795-1-bharadwaj.raju777@gmail.com/T/">my patch</a> to the bcachefs mailing list, only to be told by Kent Overstreet that he had beat me to it. But! He hadn't replaced the <code>bch2_print_string_as_lines</code> call, which meant the issue wasn’t fully fixed, and I still had a contribution to make. I made <a href="https://lore.kernel.org/linux-bcachefs/20250402181556.81529-1-bharadwaj.raju777@gmail.com/T/">another patch</a> doing only that, and it was accepted.</p>
<h3>Error Paths, 1</h3>
<p class="unjustifiable">The bug this time is <a href="https://syzkaller.appspot.com/bug?extid=cfd994b9cdf00446fd54"><abbr>UBSAN</abbr>: shift-out-of-bounds in <code>__bch2_bkey_unpack_key</code></a>.</p>

<p>Crash report:</p>
<pre><code>UBSAN: shift-out-of-bounds in fs/bcachefs/bkey.c:163:16
shift exponent 4294967127 is too large for 64-bit type 'u64' (aka 'unsigned long long')
CPU: 0 UID: 0 PID: 5832 Comm: read_btree_node Not tainted 6.15.0-syzkaller-01958-g785cdec46e92 #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/07/2025
Call Trace:
 <TASK>
 dump_stack_lvl+0x189/0x250 lib/dump_stack.c:120
 ubsan_epilogue+0xa/0x40 lib/ubsan.c:231
 __ubsan_handle_shift_out_of_bounds+0x386/0x410 lib/ubsan.c:492
 get_inc_field fs/bcachefs/bkey.c:163 [inline]
 __bch2_bkey_unpack_key+0xdc4/0xe10 fs/bcachefs/bkey.c:284
 __bch2_bkey_compat+0x4db/0xbd0 fs/bcachefs/bkey_methods.c:480
 bch2_bkey_compat fs/bcachefs/bkey_methods.h:134 [inline]
 validate_bset_keys+0x6c1/0x1390 fs/bcachefs/btree_io.c:983
 bch2_btree_node_read_done+0x18c8/0x4f60 fs/bcachefs/btree_io.c:1211
 btree_node_read_work+0x426/0xe30 fs/bcachefs/btree_io.c:1400
 bch2_btree_node_read+0x887/0x29f0 fs/bcachefs/btree_io.c:-1
 bch2_btree_node_fill+0xd12/0x14f0 fs/bcachefs/btree_cache.c:994
 bch2_btree_node_get_noiter+0xa2c/0x1000 fs/bcachefs/btree_cache.c:1261
 found_btree_node_is_readable fs/bcachefs/btree_node_scan.c:85 [inline]
 try_read_btree_node fs/bcachefs/btree_node_scan.c:220 [inline]
 read_btree_nodes_worker+0x1319/0x1e20 fs/bcachefs/btree_node_scan.c:269
 kthread+0x711/0x8a0 kernel/kthread.c:464
 ret_from_fork+0x4e/0x80 arch/x86/kernel/process.c:148
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>
---[ end trace ]---</code></pre>
<p>That shift exponent does look too large. Let’s check out the function where it happens.</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/bkey.c?id=785cdec46e9227f9433884ed3b436471e944007c#n163">fs/bcachefs/bkey.c</a></div><pre><code><span class=n>__always_inline</span>
<span class=k>static</span><span class=w> </span><span class=n>u64</span><span class=w> </span><span class=n>get_inc_field</span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>unpack_state</span><span class=w> </span><span class=o>*</span><span class=n>state</span><span class=p>,</span><span class=w> </span><span class=kt>unsigned</span><span class=w> </span><span class=n>field</span><span class=p>)</span>
<span class=p>{</span>
<span class=w>	</span><span class=kt>unsigned</span><span class=w> </span><span class=n>bits</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>state</span><span class=o>-></span><span class=n>format</span><span class=o>-></span><span class=n>bits_per_field</span><span class=p>[</span><span class=n>field</span><span class=p>];</span>
<span class=w>	</span><span class=n>u64</span><span class=w> </span><span class=n>v</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=mi>0</span><span class=p>,</span><span class=w> </span><span class=n>offset</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>le64_to_cpu</span><span class=p>(</span><span class=n>state</span><span class=o>-></span><span class=n>format</span><span class=o>-></span><span class=n>field_offset</span><span class=p>[</span><span class=n>field</span><span class=p>]);</span>

<span class=w>	</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=n>bits</span><span class=w> </span><span class=o>>=</span><span class=w> </span><span class=n>state</span><span class=o>-></span><span class=n>bits</span><span class=p>)</span><span class=w> </span><span class=p>{</span><hll><span class=w>		</span><span class=n>v</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>state</span><span class=o>-></span><span class=n>w</span><span class=w> </span><span class=o>>></span><span class=w> </span><span class=p>(</span><span class=mi>64</span><span class=w> </span><span class=o>-</span><span class=w> </span><span class=n>bits</span><span class=p>);</span></hll><span class=w>		</span><span class=n>bits</span><span class=w> </span><span class=o>-=</span><span class=w> </span><span class=n>state</span><span class=o>-></span><span class=n>bits</span><span class=p>;</span>

<span class=w>		</span><span class=n>state</span><span class=o>-></span><span class=n>p</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>next_word</span><span class=p>(</span><span class=n>state</span><span class=o>-></span><span class=n>p</span><span class=p>);</span>
<span class=w>		</span><span class=n>state</span><span class=o>-></span><span class=n>w</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=o>*</span><span class=n>state</span><span class=o>-></span><span class=n>p</span><span class=p>;</span>
<span class=w>		</span><span class=n>state</span><span class=o>-></span><span class=n>bits</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=mi>64</span><span class=p>;</span>
<span class=w>	</span><span class=p>}</span>

<span class=w>	</span><span class=cm>/* avoid shift by 64 if bits is 0 - bits is never 64 here: */</span>
<span class=w>	</span><span class=n>v</span><span class=w> </span><span class=o>|=</span><span class=w> </span><span class=p>(</span><span class=n>state</span><span class=o>-></span><span class=n>w</span><span class=w> </span><span class=o>>></span><span class=w> </span><span class=mi>1</span><span class=p>)</span><span class=w> </span><span class=o>>></span><span class=w> </span><span class=p>(</span><span class=mi>63</span><span class=w> </span><span class=o>-</span><span class=w> </span><span class=n>bits</span><span class=p>);</span>
<span class=w>	</span><span class=n>state</span><span class=o>-></span><span class=n>w</span><span class=w> </span><span class=o><<=</span><span class=w> </span><span class=n>bits</span><span class=p>;</span>
<span class=w>	</span><span class=n>state</span><span class=o>-></span><span class=n>bits</span><span class=w> </span><span class=o>-=</span><span class=w> </span><span class=n>bits</span><span class=p>;</span>

<span class=w>	</span><span class=k>return</span><span class=w> </span><span class=n>v</span><span class=w> </span><span class=o>+</span><span class=w> </span><span class=n>offset</span><span class=p>;</span>
<span class=p>}</span>
</code></pre> </div>
<p><code><span class="p">(</span><span class="mi">64</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">bits</span><span class="p">)</span></code> became <code><span class="mi">4294967127</span></code>. Shift exponents are unsigned, so <code>bits</code> must have been greater than 64 to cause a huge value when cast. We can calculate (or <code>printk</code>) to see that <code><span class="n">bits</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">233</span></code>.</p>

<p>So <code><span class="n">state</span><span class="o">-&gt;</span><span class="n">format</span></code> has invalid values. Where does it come from? If we follow it up the call stack, we see that it comes from <code><span class="k">struct</span><span class="w"> </span><span class="nc">btree</span></code> itself and is passed here:</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/btree_io.c?id=785cdec46e9227f9433884ed3b436471e944007c#n983">fs/bcachefs/btree_io.c</a></div><pre><code><span class=k>static</span><span class=w> </span><span class=kt>int</span><span class=w> </span><span class=nf>validate_bset_keys</span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>bch_fs</span><span class=w> </span><span class=o>*</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=k>struct</span><span class=w> </span><span class=nc>btree</span><span class=w> </span><span class=o>*</span><span class=n>b</span><span class=p>,</span>
<span class=w>			 </span><span class=k>struct</span><span class=w> </span><span class=nc>bset</span><span class=w> </span><span class=o>*</span><span class=n>i</span><span class=p>,</span><span class=w> </span><span class=kt>int</span><span class=w> </span><span class=n>write</span><span class=p>,</span>
<span class=w>			 </span><span class=k>struct</span><span class=w> </span><span class=nc>bch_io_failures</span><span class=w> </span><span class=o>*</span><span class=n>failed</span><span class=p>,</span>
<span class=w>			 </span><span class=k>struct</span><span class=w> </span><span class=nc>printbuf</span><span class=w> </span><span class=o>*</span><span class=n>err_msg</span><span class=p>)</span>
<span class=p>{</span>

<span class=w>		</span>…

<span class=w>		</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=o>!</span><span class=n>write</span><span class=p>)</span>
<span class=w>			</span><span class=n>bch2_bkey_compat</span><span class=p>(</span><span class=n>b</span><span class=o>-></span><span class=n>c</span><span class=p>.</span><span class=n>level</span><span class=p>,</span><span class=w> </span><span class=n>b</span><span class=o>-></span><span class=n>c</span><span class=p>.</span><span class=n>btree_id</span><span class=p>,</span><span class=w> </span><span class=n>version</span><span class=p>,</span>
<span class=w>				    </span><span class=n>BSET_BIG_ENDIAN</span><span class=p>(</span><span class=n>i</span><span class=p>),</span><span class=w> </span><span class=n>write</span><span class=p>,</span><hll><span class=w>				    </span><span class=o>&</span><span class=n>b</span><span class=o>-></span><span class=n>format</span><span class=p>,</span><span class=w> </span><span class=n>k</span><span class=p>);</span></hll>
<span class=w>		</span>…

<span class=p>}</span>
</code></pre> </div>
<p class="unjustifiable">Well… what now? Let’s look at where <code>validate_bset_keys</code> is being called from here.</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/btree_io.c?id=785cdec46e9227f9433884ed3b436471e944007c#n1211">fs/bcachefs/btree_io.c</a></div><pre><code><span class=kt>int</span><span class=w> </span><span class=nf>bch2_btree_node_read_done</span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>bch_fs</span><span class=w> </span><span class=o>*</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=k>struct</span><span class=w> </span><span class=nc>bch_dev</span><span class=w> </span><span class=o>*</span><span class=n>ca</span><span class=p>,</span>
<span class=w>			      </span><span class=k>struct</span><span class=w> </span><span class=nc>btree</span><span class=w> </span><span class=o>*</span><span class=n>b</span><span class=p>,</span>
<span class=w>			      </span><span class=k>struct</span><span class=w> </span><span class=nc>bch_io_failures</span><span class=w> </span><span class=o>*</span><span class=n>failed</span><span class=p>,</span>
<span class=w>			      </span><span class=k>struct</span><span class=w> </span><span class=nc>printbuf</span><span class=w> </span><span class=o>*</span><span class=n>err_msg</span><span class=p>)</span>
<span class=p>{</span>
<span class=w>		</span>…
<span class=w>    </span>
<span class=w>		</span><span class=n>ret</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>validate_bset</span><span class=p>(</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=n>ca</span><span class=p>,</span><span class=w> </span><span class=n>b</span><span class=p>,</span><span class=w> </span><span class=n>i</span><span class=p>,</span><span class=w> </span><span class=n>b</span><span class=o>-></span><span class=n>written</span><span class=p>,</span><span class=w> </span><span class=n>sectors</span><span class=p>,</span><span class=w> </span><span class=n>READ</span><span class=p>,</span><span class=w> </span><span class=n>failed</span><span class=p>,</span><span class=w> </span><span class=n>err_msg</span><span class=p>);</span>
<span class=w>		</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=n>ret</span><span class=p>)</span>
<span class=w>			</span><span class=k>goto</span><span class=w> </span><span class=n>fsck_err</span><span class=p>;</span>

<span class=w>		</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=o>!</span><span class=n>b</span><span class=o>-></span><span class=n>written</span><span class=p>)</span>
<span class=w>			</span><span class=n>btree_node_set_format</span><span class=p>(</span><span class=n>b</span><span class=p>,</span><span class=w> </span><span class=n>b</span><span class=o>-></span><span class=n>data</span><span class=o>-></span><span class=n>format</span><span class=p>);</span>
<hll><span class=w>		</span><span class=n>ret</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>validate_bset_keys</span><span class=p>(</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=n>b</span><span class=p>,</span><span class=w> </span><span class=n>i</span><span class=p>,</span><span class=w> </span><span class=n>READ</span><span class=p>,</span><span class=w> </span><span class=n>failed</span><span class=p>,</span><span class=w> </span><span class=n>err_msg</span><span class=p>);</span></hll><span class=w>		</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=n>ret</span><span class=p>)</span>
<span class=w>			</span><span class=k>goto</span><span class=w> </span><span class=n>fsck_err</span><span class=p>;</span>

<span class=w>		</span>…
<span class=p>}</span>
</code></pre> </div>
<p class="unjustifiable">There’s a lot of validation functions in the surrounding code, which is interesting. If <code>validate_bset_keys</code> fails on an invalid format, then maybe it expects a previous validation step to have caught it? There’s some reference to <code>btree_node_set_format</code> immediately after <code>validate_bset</code>, so let’s see if there's anything relevant to us there. Turns out there isn’t any validation in <code>btree_node_set_format</code> itself, so maybe it’s happening before. Let’s read <code>validate_bset</code> instead. It’s a huge function, nevertheless a careful reading rewards us:</p>

<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/btree_io.c?id=785cdec46e9227f9433884ed3b436471e944007c#n861">fs/bcachefs/btree_io.c</a></div><pre><code><span class=k>static</span><span class=w> </span><span class=kt>int</span><span class=w> </span><span class=nf>validate_bset</span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>bch_fs</span><span class=w> </span><span class=o>*</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=k>struct</span><span class=w> </span><span class=nc>bch_dev</span><span class=w> </span><span class=o>*</span><span class=n>ca</span><span class=p>,</span>
<span class=w>			 </span><span class=k>struct</span><span class=w> </span><span class=nc>btree</span><span class=w> </span><span class=o>*</span><span class=n>b</span><span class=p>,</span><span class=w> </span><span class=k>struct</span><span class=w> </span><span class=nc>bset</span><span class=w> </span><span class=o>*</span><span class=n>i</span><span class=p>,</span>
<span class=w>			 </span><span class=kt>unsigned</span><span class=w> </span><span class=n>offset</span><span class=p>,</span><span class=w> </span><span class=kt>unsigned</span><span class=w> </span><span class=n>sectors</span><span class=p>,</span><span class=w> </span><span class=kt>int</span><span class=w> </span><span class=n>write</span><span class=p>,</span>
<span class=w>			 </span><span class=k>struct</span><span class=w> </span><span class=nc>bch_io_failures</span><span class=w> </span><span class=o>*</span><span class=n>failed</span><span class=p>,</span>
<span class=w>			 </span><span class=k>struct</span><span class=w> </span><span class=nc>printbuf</span><span class=w> </span><span class=o>*</span><span class=n>err_msg</span><span class=p>)</span>
<span class=p>{</span>
<span class=w>		</span>…
<hll><span class=w>		</span><span class=n>btree_err_on</span><span class=p>(</span><span class=n>bch2_bkey_format_invalid</span><span class=p>(</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=o>&</span><span class=n>bn</span><span class=o>-></span><span class=n>format</span><span class=p>,</span><span class=w> </span><span class=n>write</span><span class=p>,</span><span class=w> </span><span class=o>&</span><span class=n>buf1</span><span class=p>),</span></hll><span class=w>			     </span><span class=o>-</span><span class=n>BCH_ERR_btree_node_read_err_bad_node</span><span class=p>,</span>
<span class=w>			     </span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=n>ca</span><span class=p>,</span><span class=w> </span><span class=n>b</span><span class=p>,</span><span class=w> </span><span class=n>i</span><span class=p>,</span><span class=w> </span><span class=nb>NULL</span><span class=p>,</span>
<span class=w>			     </span><span class=n>btree_node_bad_format</span><span class=p>,</span>
<span class=w>			     </span><span class=s>"invalid bkey format: %s</span><span class=se>\n</span><span class=s>%s"</span><span class=p>,</span><span class=w> </span><span class=n>buf1</span><span class=p>.</span><span class=n>buf</span><span class=p>,</span>
<span class=w>			     </span><span class=p>(</span><span class=n>printbuf_reset</span><span class=p>(</span><span class=o>&</span><span class=n>buf2</span><span class=p>),</span>
<span class=w>			      </span><span class=n>bch2_bkey_format_to_text</span><span class=p>(</span><span class=o>&</span><span class=n>buf2</span><span class=p>,</span><span class=w> </span><span class=o>&</span><span class=n>bn</span><span class=o>-></span><span class=n>format</span><span class=p>),</span><span class=w> </span><span class=n>buf2</span><span class=p>.</span><span class=n>buf</span><span class=p>));</span>

<span class=w>		</span>…
<span class=p>}</span>
</code></pre> </div>
<p class="unjustifiable">So there is a function to validate the format after all! So, the question becomes: why did it not catch the invalid format? Adding a <code>printk</code> to log its result tells us that it <em>does</em> return <code><span class="o">-</span><span class="n">BCH_ERR_invalid</span></code>. What gives?</p>

<p class="unjustifiable">The answer is in <code>btree_err_on</code>, and how it deals with <code>btree_node_bad_format</code> errors.</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/btree_io.c?id=785cdec46e9227f9433884ed3b436471e944007c#n656">fs/bcachefs/btree_io.c</a></div><pre><code><span class=cp>#define btree_err(type, c, ca, b, i, k, _err_type, msg, ...)		\ </span>
<span class=p>({</span><span class=w>									</span>\<span class=w> </span>
<span class=w>	</span><span class=kt>int</span><span class=w> </span><span class=n>_ret</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>__btree_err</span><span class=p>(</span><span class=n>type</span><span class=p>,</span><span class=w> </span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=n>ca</span><span class=p>,</span><span class=w> </span><span class=n>b</span><span class=p>,</span><span class=w> </span><span class=n>i</span><span class=p>,</span><span class=w> </span><span class=n>k</span><span class=p>,</span><span class=w> </span><span class=n>write</span><span class=p>,</span><span class=w>		</span>\<span class=w> </span>
<span class=w>			       </span><span class=n>BCH_FSCK_ERR_</span>##<span class=n>_err_type</span><span class=p>,</span><span class=w>		</span>\<span class=w> </span>
<span class=w>			       </span><span class=n>failed</span><span class=p>,</span><span class=w> </span><span class=n>err_msg</span><span class=p>,</span><span class=w>				</span>\<span class=w> </span>
<span class=w>			       </span><span class=n>msg</span><span class=p>,</span><span class=w> </span>##<span class=n>__VA_ARGS__</span><span class=p>);</span><span class=w>			</span>\<span class=w> </span>
<span class=w>									</span>\<span class=w> </span>
<span class=w>									</span>\<span class=w> </span><hll><span class=w>	</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=n>_ret</span><span class=w> </span><span class=o>!=</span><span class=w> </span><span class=o>-</span><span class=n>BCH_ERR_fsck_fix</span><span class=p>)</span><span class=w> </span><span class=p>{</span><span class=w>				</span>\<span class=w> </span></hll><span class=w>		</span><span class=n>ret</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>_ret</span><span class=p>;</span><span class=w>						</span>\<span class=w> </span>
<span class=w>		</span><span class=k>goto</span><span class=w> </span><span class=n>fsck_err</span><span class=p>;</span><span class=w>						</span>\<span class=w> </span>
<span class=w>	</span><span class=p>}</span><span class=w>								</span>\<span class=w> </span>
<span class=w>									</span>\<span class=w> </span>
<span class=w>	</span><span class=nb>true</span><span class=p>;</span><span class=w>								</span>\<span class=w> </span>
<span class=p>})</span>

<span class=cp>#define btree_err_on(cond, ...)	((cond) ? btree_err(__VA_ARGS__) : false)</span>
<span class=p>}</span>
</code></pre> </div>
<p class="unjustifiable">So, the jump to <code>fsck_err</code> is only made if the return value from <code>__btree_err</code> is not <code><span class="o">-</span><span class="n">BCH_ERR_fsck_fix</span></code>. Let’s look at what <code>__btree_err</code> does, then.</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/btree_io.c?id=785cdec46e9227f9433884ed3b436471e944007c#n546">fs/bcachefs/btree_io.c</a></div><pre><code><span class=n>__printf</span><span class=p>(</span><span class=mi>11</span><span class=p>,</span><span class=w> </span><span class=mi>12</span><span class=p>)</span>
<span class=k>static</span><span class=w> </span><span class=kt>int</span><span class=w> </span><span class=n>__btree_err</span><span class=p>(</span><span class=kt>int</span><span class=w> </span><span class=n>ret</span><span class=p>,</span>
<span class=w>		       </span><span class=k>struct</span><span class=w> </span><span class=nc>bch_fs</span><span class=w> </span><span class=o>*</span><span class=n>c</span><span class=p>,</span>
<span class=w>		       </span><span class=k>struct</span><span class=w> </span><span class=nc>bch_dev</span><span class=w> </span><span class=o>*</span><span class=n>ca</span><span class=p>,</span>
<span class=w>		       </span><span class=k>struct</span><span class=w> </span><span class=nc>btree</span><span class=w> </span><span class=o>*</span><span class=n>b</span><span class=p>,</span>
<span class=w>		       </span><span class=k>struct</span><span class=w> </span><span class=nc>bset</span><span class=w> </span><span class=o>*</span><span class=n>i</span><span class=p>,</span>
<span class=w>		       </span><span class=k>struct</span><span class=w> </span><span class=nc>bkey_packed</span><span class=w> </span><span class=o>*</span><span class=n>k</span><span class=p>,</span>
<span class=w>		       </span><span class=kt>int</span><span class=w> </span><span class=n>rw</span><span class=p>,</span>
<span class=w>		       </span><span class=k>enum</span><span class=w> </span><span class=n>bch_sb_error_id</span><span class=w> </span><span class=n>err_type</span><span class=p>,</span>
<span class=w>		       </span><span class=k>struct</span><span class=w> </span><span class=nc>bch_io_failures</span><span class=w> </span><span class=o>*</span><span class=n>failed</span><span class=p>,</span>
<span class=w>		       </span><span class=k>struct</span><span class=w> </span><span class=nc>printbuf</span><span class=w> </span><span class=o>*</span><span class=n>err_msg</span><span class=p>,</span>
<span class=w>		       </span><span class=k>const</span><span class=w> </span><span class=kt>char</span><span class=w> </span><span class=o>*</span><span class=n>fmt</span><span class=p>,</span><span class=w> </span><span class=p>...)</span>
<span class=p>{</span><hll><span class=w>	</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=n>c</span><span class=o>-></span><span class=n>recovery</span><span class=p>.</span><span class=n>curr_pass</span><span class=w> </span><span class=o>==</span><span class=w> </span><span class=n>BCH_RECOVERY_PASS_scan_for_btree_nodes</span><span class=p>)</span>
<span class=w>		</span><span class=k>return</span><span class=w> </span><span class=o>-</span><span class=n>BCH_ERR_fsck_fix</span><span class=p>;</span></hll>
<span class=w>	</span>…
<span class=p>}</span>
</code></pre> </div>
<p class="unjustifiable">And that’s it (confirmed by a bit of logging). If the current recovery pass is “scan for btree nodes”, then all kinds of errors will be turned into <code><span class="o">-</span><span class="n">BCH_ERR_fsck_fix</span></code>.</p>

<p class="unjustifiable">This offending line was added in <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=cd3cdb1ef706a1ac725194d81858d58375739b25"><code>cd3cdb1ef706</code></a>. Previously it would have returned <code><span class="n">__bch2_topology_error</span><span class="p">(</span><span class="n">c</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">out</span><span class="p">)</span></code>.</p>

<p class="unjustifiable">Based on that I made my <a href="https://lore.kernel.org/all/20250614185743.657564-1-bharadwaj.raju777@gmail.com/T/">first patch</a>, but based on feedback I sent a simpler <a href="https://lore.kernel.org/all/20250615164547.11900-1-bharadwaj.raju777@gmail.com/T/">second patch</a>, which was accepted. The function now checks if it is <code><span class="o">-</span><span class="n">BCH_ERR_btree_node_read_err_fixable</span></code> before returning <code>fsck_fix</code>.</p>
<h3>Error Paths, 2</h3>
<p class="unjustifiable">The bug at hand: <a href="https://syzkaller.appspot.com/bug?extid=029d1989099aa5ae3e89"><abbr>UBSAN</abbr>: shift-out-of-bounds in <code>__bch2_btree_node_hash_insert</code></a>.</p>
<pre><code>  node offset 0/16 bset u64s 0: incorrect max key U64_MAX:18374686479671623680:50331647, btree topology error: 
bcachefs (loop0): flagging btree xattrs lost data
bcachefs (loop0): running explicit recovery pass check_backpointers_to_extents (16), currently at recovery_pass_empty (0)
bcachefs (loop0): running explicit recovery pass scan_for_btree_nodes (1), currently at recovery_pass_empty (0)
bcachefs (loop0): error reading btree root btree=xattrs level=0: btree_node_read_error, fixing
------------[ cut here ]------------
UBSAN: shift-out-of-bounds in fs/bcachefs/btree_cache.c:218:18
shift exponent 251 is too large for 64-bit type 'unsigned long long'
CPU: 0 UID: 0 PID: 5830 Comm: syz-executor323 Not tainted 6.15.0-rc1-syzkaller-00246-g900241a5cc15 #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2025
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x241/0x360 lib/dump_stack.c:120
 ubsan_epilogue lib/ubsan.c:231 [inline]
 __ubsan_handle_shift_out_of_bounds+0x3c8/0x420 lib/ubsan.c:492
 __btree_node_pinned fs/bcachefs/btree_cache.c:218 [inline]
 __bch2_btree_node_hash_insert+0x1b32/0x1ba0 fs/bcachefs/btree_cache.c:294
 bch2_btree_node_hash_insert+0x7e/0xc0 fs/bcachefs/btree_cache.c:309
 __bch2_btree_root_read fs/bcachefs/btree_io.c:1791 [inline]
 bch2_btree_root_read+0x605/0x7e0 fs/bcachefs/btree_io.c:1819
 read_btree_roots+0x3d7/0xa80 fs/bcachefs/recovery.c:581
 bch2_fs_recovery+0x28e4/0x3e20 fs/bcachefs/recovery.c:928
 bch2_fs_start+0x310/0x620 fs/bcachefs/super.c:1059
 bch2_fs_get_tree+0x113e/0x18f0 fs/bcachefs/fs.c:2253
 vfs_get_tree+0x90/0x2b0 fs/super.c:1759
 do_new_mount+0x2cf/0xb70 fs/namespace.c:3879
 do_mount fs/namespace.c:4219 [inline]
 __do_sys_mount fs/namespace.c:4430 [inline]
 __se_sys_mount+0x38c/0x400 fs/namespace.c:4407
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xf3/0x230 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f3aa1b3afaa
Code: d8 64 89 02 48 c7 c0 ff ff ff ff eb a6 e8 5e 04 00 00 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 49 89 ca b8 a5 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffe042c3768 EFLAGS: 00000282 ORIG_RAX: 00000000000000a5
RAX: ffffffffffffffda RBX: 00007ffe042c3780 RCX: 00007f3aa1b3afaa
RDX: 0000200000000180 RSI: 0000200000000540 RDI: 00007ffe042c3780
RBP: 0000200000000540 R08: 00007ffe042c37c0 R09: 0000000000005964
R10: 0000000000800000 R11: 0000000000000282 R12: 0000200000000180
R13: 00007ffe042c37c0 R14: 0000000000000003 R15: 0000000000800000
 </TASK>
---[ end trace ]---</code></pre>
<p>Let’s look at the last function to see what exactly is invalid.</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/btree_cache.c?id=900241a5cc15e6e0709a012051cc72d224cd6a6e#n218">fs/bcachefs/btree_cache.c</a></div><pre><code><span class=k>static</span><span class=w> </span><span class=kr>inline</span><span class=w> </span><span class=kt>bool</span><span class=w> </span><span class=nf>__btree_node_pinned</span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>btree_cache</span><span class=w> </span><span class=o>*</span><span class=n>bc</span><span class=p>,</span><span class=w> </span><span class=k>struct</span><span class=w> </span><span class=nc>btree</span><span class=w> </span><span class=o>*</span><span class=n>b</span><span class=p>)</span>
<span class=p>{</span>
<span class=w>	</span><span class=k>struct</span><span class=w> </span><span class=nc>bbpos</span><span class=w> </span><span class=n>pos</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>BBPOS</span><span class=p>(</span><span class=n>b</span><span class=o>-></span><span class=n>c</span><span class=p>.</span><span class=n>btree_id</span><span class=p>,</span><span class=w> </span><span class=n>b</span><span class=o>-></span><span class=n>key</span><span class=p>.</span><span class=n>k</span><span class=p>.</span><span class=n>p</span><span class=p>);</span>

<span class=w>	</span><span class=n>u64</span><span class=w> </span><span class=n>mask</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>bc</span><span class=o>-></span><span class=n>pinned_nodes_mask</span><span class=p>[</span><span class=o>!!</span><span class=n>b</span><span class=o>-></span><span class=n>c</span><span class=p>.</span><span class=n>level</span><span class=p>];</span>
<hll><span class=w>	</span><span class=k>return</span><span class=w> </span><span class=p>((</span><span class=n>mask</span><span class=w> </span><span class=o>&</span><span class=w> </span><span class=n>BIT_ULL</span><span class=p>(</span><span class=n>b</span><span class=o>-></span><span class=n>c</span><span class=p>.</span><span class=n>btree_id</span><span class=p>))</span><span class=w> </span><span class=o>&&</span></hll><span class=w>		</span><span class=n>bbpos_cmp</span><span class=p>(</span><span class=n>bc</span><span class=o>-></span><span class=n>pinned_nodes_start</span><span class=p>,</span><span class=w> </span><span class=n>pos</span><span class=p>)</span><span class=w> </span><span class=o><</span><span class=w> </span><span class=mi>0</span><span class=w> </span><span class=o>&&</span>
<span class=w>		</span><span class=n>bbpos_cmp</span><span class=p>(</span><span class=n>bc</span><span class=o>-></span><span class=n>pinned_nodes_end</span><span class=p>,</span><span class=w> </span><span class=n>pos</span><span class=p>)</span><span class=w> </span><span class=o>>=</span><span class=w> </span><span class=mi>0</span><span class=p>);</span>
<span class=p>}</span>
</code></pre> </div>
<p><code><span class="n">BIT_ULL</span><span class="p">(</span><span class="n">nr</span><span class="p">)</span></code> expands to <code><span class="mi">1ULL</span><span class="w"> </span><span class="o">&lt;&lt;</span><span class="w"> </span><span class="n">nr</span></code>. So <code><span class="n">b</span><span class="o">-&gt;</span><span class="n">c</span><span class="p">.</span><span class="n">btree_id</span></code> is too large at 251.</p>

<p>But… what are the valid values for <code>btree_id</code>? Where does it come from, and where is it validated, if anywhere?</p>

<p>There are many ways you could try to research these questions. What I did was just search for <code>btree_id</code> in the codebase and scan through for interesting instances.</p>
            <small class="sidenote marginnote">&#8203;<span class="sidenote-surroundings">&nbsp;(</span>With some more forethought, you could save time by assuming that a validation check, if it existed, would probably be in the form of <code><span class="n">btree_id</span><span class="o">\</span><span class="n">s</span><span class="o">+</span><span class="p">(</span><span class="o">&gt;|&lt;</span><span class="p">)</span></code> and search for that.<span class="sidenote-surroundings">)&nbsp;</span></small>
<p>I found this:</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/recovery.c?id=900241a5cc15e6e0709a012051cc72d224cd6a6e#n469">fs/bcachefs/recovery.c</a></div><pre><code><span class=k>static</span><span class=w> </span><span class=kt>int</span><span class=w> </span><span class=nf>journal_replay_entry_early</span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>bch_fs</span><span class=w> </span><span class=o>*</span><span class=n>c</span><span class=p>,</span>
<span class=w>				      </span><span class=k>struct</span><span class=w> </span><span class=nc>jset_entry</span><span class=w> </span><span class=o>*</span><span class=n>entry</span><span class=p>)</span>
<span class=p>{</span>
<span class=w>	</span><span class=kt>int</span><span class=w> </span><span class=n>ret</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=mi>0</span><span class=p>;</span>

<span class=w>	</span><span class=k>switch</span><span class=w> </span><span class=p>(</span><span class=n>entry</span><span class=o>-></span><span class=n>type</span><span class=p>)</span><span class=w> </span><span class=p>{</span>
<span class=w>	</span><span class=k>case</span><span class=w> </span><span class=no>BCH_JSET_ENTRY_btree_root</span><span class=p>:</span><span class=w> </span><span class=p>{</span>

<span class=w>		</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=n>unlikely</span><span class=p>(</span><span class=o>!</span><span class=n>entry</span><span class=o>-></span><span class=n>u64s</span><span class=p>))</span>
<span class=w>			</span><span class=k>return</span><span class=w> </span><span class=mi>0</span><span class=p>;</span>
<hll><span class=w>		</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=n>fsck_err_on</span><span class=p>(</span><span class=n>entry</span><span class=o>-></span><span class=n>btree_id</span><span class=w> </span><span class=o>>=</span><span class=w> </span><span class=n>BTREE_ID_NR_MAX</span><span class=p>,</span>
<span class=w>				</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=n>invalid_btree_id</span><span class=p>,</span>
<span class=w>				</span><span class=s>"invalid btree id %u (max %u)"</span><span class=p>,</span>
<span class=w>				</span><span class=n>entry</span><span class=o>-></span><span class=n>btree_id</span><span class=p>,</span><span class=w> </span><span class=n>BTREE_ID_NR_MAX</span><span class=p>))</span></hll><span class=w>			</span><span class=k>return</span><span class=w> </span><span class=mi>0</span><span class=p>;</span>

<span class=w>		</span>…
<span class=p>}</span>
</code></pre> </div>
<p>Examining the functions in the call trace will show that this function is indeed called before the failure point.</p>

<p>This seems familiar to the previous case, doesn’t it? There’s a validation check in place, but it doesn't actually stop the code from proceeding, for whatever reason. Let’s find out.</p>

<p>This check was introduced in <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9e7cfb35e2668e542c333ed3ec4b0a951dd332ee"><code>9e7cfb35e266</code></a>. A later commit, <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=14152654805256d760315ec24e414363bfa19a06"><code>141526548052</code></a>, introduced this bug. It is worthwhile to see what exactly these two commits do.</p>

<p class="unjustifiable">The first introduces a new error type called <code>invalid_btree_id</code> in <code>sb-errors_format.h</code>.            <small class="sidenote marginnote">&#8203;<span class="sidenote-surroundings">&nbsp;(</span><code>sb</code> stands for superblock.<span class="sidenote-surroundings">)&nbsp;</span></small> The second marks the error types <code>btree_root_bkey_invalid</code> and <code>btree_root_read_error</code> as <code>FSCK_AUTOFIX</code>. Why did that make the bug happen? Look at these lines from the crash report, just before <abbr>UBSAN</abbr> blows up:</p>
<pre><code>  node offset 0/16 bset u64s 0: incorrect max key U64_MAX:18374686479671623680:50331647, btree topology error: 
bcachefs (loop0): flagging btree xattrs lost data
bcachefs (loop0): running explicit recovery pass check_backpointers_to_extents (16), currently at recovery_pass_empty (0)
bcachefs (loop0): running explicit recovery pass scan_for_btree_nodes (1), currently at recovery_pass_empty (0)<hll>bcachefs (loop0): error reading btree root btree=xattrs level=0: btree_node_read_error, fixing</hll></code></pre>
<p class="unjustifiable">It looks as if marking <code>btree_root_read_error</code> as autofix caused us to attempt to fix it (as expected), and that codepath ran into this issue.</p>

<p>Back to why the validation check didn’t work. Once again, let’s look at how <code>fsck_err_on</code> actually works. Long story short, it expands into a bunch of macros:</p>
<div class="named-codeblock"><div class="codeblock-mast"><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/bcachefs/error.h?id=900241a5cc15e6e0709a012051cc72d224cd6a6e#n90">fs/bcachefs/error.h</a></div><pre><code><span class=cp>#define bch2_fsck_err(c, _flags, _err_type, ...)				\ </span>
<span class=w>	</span><span class=n>__bch2_fsck_err</span><span class=p>(</span><span class=n>type_is</span><span class=p>(</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=k>struct</span><span class=w> </span><span class=nc>bch_fs</span><span class=w> </span><span class=o>*</span><span class=p>)</span><span class=w> </span><span class=o>?</span><span class=w> </span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>bch_fs</span><span class=w> </span><span class=o>*</span><span class=p>)</span><span class=w> </span><span class=n>c</span><span class=w> </span><span class=o>:</span><span class=w> </span><span class=nb>NULL</span><span class=p>,</span>\<span class=w> </span>
<span class=w>			</span><span class=n>type_is</span><span class=p>(</span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=k>struct</span><span class=w> </span><span class=nc>btree_trans</span><span class=w> </span><span class=o>*</span><span class=p>)</span><span class=w> </span><span class=o>?</span><span class=w> </span><span class=p>(</span><span class=k>struct</span><span class=w> </span><span class=nc>btree_trans</span><span class=w> </span><span class=o>*</span><span class=p>)</span><span class=w> </span><span class=n>c</span><span class=w> </span><span class=o>:</span><span class=w> </span><span class=nb>NULL</span><span class=p>,</span>\<span class=w> </span>
<span class=w>			</span><span class=n>_flags</span><span class=p>,</span><span class=w> </span><span class=n>BCH_FSCK_ERR_</span>##<span class=n>_err_type</span><span class=p>,</span><span class=w> </span><span class=n>__VA_ARGS__</span><span class=p>)</span><span class=w> </span>

<span class=cp>#define fsck_err_wrap(_do)						\ </span>
<span class=p>({</span><span class=w>									</span>\<span class=w> </span>
<span class=w>	</span><span class=kt>int</span><span class=w> </span><span class=n>_ret</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>_do</span><span class=p>;</span><span class=w>							</span>\<span class=w> </span><hll><span class=w>	</span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=n>_ret</span><span class=w> </span><span class=o>!=</span><span class=w> </span><span class=o>-</span><span class=n>BCH_ERR_fsck_fix</span><span class=w> </span><span class=o>&&</span><span class=w>				</span>\<span class=w> </span>
<span class=w>	    </span><span class=n>_ret</span><span class=w> </span><span class=o>!=</span><span class=w> </span><span class=o>-</span><span class=n>BCH_ERR_fsck_ignore</span><span class=p>)</span><span class=w> </span><span class=p>{</span><span class=w>				</span>\<span class=w> </span></hll><span class=w>		</span><span class=n>ret</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>_ret</span><span class=p>;</span><span class=w>						</span>\<span class=w> </span>
<span class=w>		</span><span class=k>goto</span><span class=w> </span><span class=n>fsck_err</span><span class=p>;</span><span class=w>						</span>\<span class=w> </span>
<span class=w>	</span><span class=p>}</span><span class=w>								</span>\<span class=w> </span>
<span class=w>									</span>\<span class=w> </span>
<span class=w>	</span><span class=n>_ret</span><span class=w> </span><span class=o>==</span><span class=w> </span><span class=o>-</span><span class=n>BCH_ERR_fsck_fix</span><span class=p>;</span><span class=w>					</span>\<span class=w> </span>
<span class=p>})</span>

<span class=cp>#define __fsck_err(...)		fsck_err_wrap(bch2_fsck_err(__VA_ARGS__))</span>

<span class=cp>#define fsck_err_on(cond, c, _err_type, ...)				\ </span>
<span class=w>	</span><span class=n>__fsck_err_on</span><span class=p>(</span><span class=n>cond</span><span class=p>,</span><span class=w> </span><span class=n>c</span><span class=p>,</span><span class=w> </span><span class=n>FSCK_CAN_FIX</span><span class=o>|</span><span class=n>FSCK_CAN_IGNORE</span><span class=p>,</span><span class=w> </span><span class=n>_err_type</span><span class=p>,</span><span class=w> </span><span class=n>__VA_ARGS__</span><span class=p>)</span>
<span class=p>}</span>
</code></pre> </div>
<p>We only <code><span class="k">goto</span><span class="w"> </span><span class="n">fsck_err</span></code> if what <code>__bch2_fsck_err</code> returns is neither <code>fsck_fix</code> nor <code>fsck_ignore</code>.</p>

<p class="unjustifiable">My <a href="https://lore.kernel.org/all/20250627084033.614376-2-bharadwaj.raju777@gmail.com/T/">first patch</a> thus proposed that we use <code>mustfix_fsck_err_on</code> instead of <code>fsck_err_on</code>, which doesn’t pass <code>FSCK_CAN_IGNORE</code> as part of the <code>_flags</code>. However, it was pointed out that we can just mark <code>invalid_btree_id</code> autofix as well, and then <code>__bch2_fsck_err</code> would (auto)fix it. So that's what my <a href="https://lore.kernel.org/all/20250627164132.25133-1-bharadwaj.raju777@gmail.com/T/">second patch</a> did, and was accepted.</p>
<h3>False Positives from <abbr>KMSAN</abbr></h3>
<p class="unjustifiable">I spent a lot of time trying and failing to investigate this bug: <a href="https://syzkaller.appspot.com/bug?extid=655143dc5f99972b52e6"><abbr>KMSAN</abbr>: uninit-value in <code>bch2_btree_ptr_v2_validate</code></a>. I did not see how the value in question was possibly uninitialized.</p>

<p>Until shortly after, when this patch series was posted to the linux-bcachefs mailing list which showed that most of the <abbr>KMSAN</abbr> bugs recorded against bcachefs were spurious: <a href="https://lore.kernel.org/linux-bcachefs/20250320213256.3359777-1-kent.overstreet@linux.dev/T/">[PATCH&nbsp;0/5] kmsan splat fixes</a>.</p>

<p><abbr>KMSAN</abbr> doesn’t understand <code>memcpy</code>s implemented using inline assembly, and it apparently has trouble when a struct's fields are initialized via stores to bitfields.</p>

<p>I learned that I could stand to take a little more seriously my suspicions of sanitizers when their reports didn’t seem to match up to what I saw. I had it in the back of my mind, but my inexperience made me apprehensive of just dismissing a report from an established tool. All in all, educational despite me not getting a bugfix out of this one.</p>

<h2>Sensor Driver</h2>
<p>I spent the rest of my time developing an <abbr>IIO</abbr> kernel driver for the InvenSense <abbr>ICM</abbr>-20948 sensor, which combines an accelerometer, gyrometer, and magnetometer. This post is long enough as it is, so I’ll talk about that another time. Maybe when it’s actually merged.</p>
<h2>Reflections</h2>
<p>I split my time during this program between bugfixes and driver development. I semi-intentionally eschewed those  contribution opportunities which were in the shape of “find and update uses of this old <abbr>API</abbr> to the new one” firstly because I did not find them that interesting to work on, and also because as I observed with my peers, <span class="sidenote-ref"><span class="sidenote-ref-text">a lot of such patches were seen as churn and rejected by maintainers</span><span class="sidenote-number"><aside class="sidenote" role="note"><!--&#8203;-->&nbsp;<span class="sidenote-surroundings">&nbsp;(</span>For a particularly incisive example, see <a href="https://lore.kernel.org/all/aHg7JOY5UrOck9ck@dread.disaster.area/">Re: [PATCH] xfs: replace strncpy with strscpy</a>.<span class="sidenote-surroundings">)&nbsp;</span></aside></span></span>.</p>

<p>Being in the <abbr>LKMP</abbr> was as rewarding as it was challenging. If you’re interested in becoming a kernel developer, I recommend it wholeheartedly. The program structure is extremely flexible, so you can do it alongside other obligations. Contributing to the Linux kernel can be rather daunting and even discouraging, so having a mentor to guide you through the initial troubles is really valuable.</p>

<p>Finally… thank you to Shuah Khan, Ricardo B. Marlière, and Javier Carrasco!</p>
]]></content>
    <link href="https://bharadwajraju.com/posts/kernel-mentorship"/>
    <published>2025-09-10T00:00:00+05:30</published>
  </entry>
  <entry>
    <id>https://bharadwajraju.com/posts/nothing-ear-2-on-linux</id>
    <title>Creating a Linux controller for the Nothing Ear (2)</title>
    <updated>2024-01-06T00:00:00+05:30</updated>
    <content type="CDATA"><![CDATA[
<p>I bought a <a href="https://nothing.tech/products/ear-2">Nothing Ear (2)</a>, and I’m mostly happy with it. It comes 
with a neat-looking Android app to let you set the noise cancellation level and equalization and such.</p>

<p>That’s fine, but it would be cool to be able to control it from my Linux computer as well. So here we are. 
We’re going to reverse-engineer the Bluetooth protocol that it speaks, and from there 
develop a Linux controller. In the spirit of <a href="https://www.youtube.com/watch?v=A9PP8AeSbbo">Nothing Chats</a>, I’m sure Carl Pei would be appreciative.</p>

<p>If you don’t want to read the technical details of how this was achieved, skip to the <a href="#the-finished-product"><em>Finished Product</em></a>.</p>
<aside class="note btw">There exists a reverse-engineered controller for the Ear (2) already! It’s called <a href="https://github.com/radiance-project/ear-pc">Ear (<abbr>PC</abbr>)</a>. It’s great and looks sleek, but unfortunately it’s Windows-only, and the actual Bluetooth communication code is not released and kept private for “the security of the users”. That’s no good for us.</aside><h2>All the things that didn’t work</h2>
<p>This was the first time I had reverse engineered anything. I ran down a lot of dead ends:<ul>  <li><em>Decompiling the actual Android app.</em>      
      
<p>The generated assembly (Smali) and decompiled Java code are hard to decipher.</p>
</li>
  <li><em>Monitoring Bluetooth traffic from my Android.</em>
      
<p>Doing this live would need root, or specialized hardware like the <a href="https://greatscottgadgets.com/ubertoothone/">Ubertooth One</a>.</p>
</li>
  <li><em>Monitoring Bluetooth traffic from <a href="https://waydro.id/">Waydroid</a>.</em>
      
<p>Waydroid <a href="https://github.com/waydroid/waydroid/issues/155">does not support Bluetooth passthrough</a>.</p>
</li></ul></p>

<p>Finally I thought of running Android-x86 in a <abbr>VM</abbr>, giving it Bluetooth, and analyzing traffic from there.            <small class="sidenote marginnote">&#8203;<span class="sidenote-surroundings">&nbsp;(</span>At this point I should thank <a href="https://wejn.org/about/">Michal Jirků</a>. While searching about snooping on 
Android-x86 traffic from the host, I found <a href="https://wejn.org/2021/04/multi-weekend-project-reversing-yamaha-yas-207-remote-control/">their series of posts on doing a very similar thing for their Yamaha <abbr>YAS-207</abbr></a>. That confirmed that I had indeed stumbled on a productive approach.<span class="sidenote-surroundings">)&nbsp;</span></small></p>
<h2>Snooping on Bluetooth from Android-x86</h2>
<p>So I set up an Android-x86 VM on VirtualBox and passed through Bluetooth.</p>
<aside class="note tip">
<p>Bluetooth didn’t work on the Android-x86 guest at first. I had to run these commands as root on the Android (from user Jikodis on <a href="https://github.com/android-x86/android-x86.github.io/issues/91#issuecomment-987475588">this issue</a>):</p>
<pre><code><span class=gp># </span>hciconfig<span class=w> </span>hci0<span class=w> </span>down
<span class=gp># </span>pm<span class=w> </span>disable<span class=w> </span>com.android.bluetooth
<span class=gp># </span>pm<span class=w> </span><span class=nb>enable</span><span class=w> </span>com.android.bluetooth
<span class=gp># </span>service<span class=w> </span>call<span class=w> </span>bluetooth_manager<span class=w> </span><span class=m>6</span>
<span class=gp># </span>hciconfig<span class=w> </span>hci0<span class=w> </span>up
</code></pre> 
<p>Try that if Bluetooth doesn’t work despite you enabling passthrough of the Bluetooth <abbr>USB</abbr> device.</p>

<p>One more thing: if you pass through Bluetooth to one <abbr>VM</abbr> session, you <em>might</em> have to reboot your host to get it back. There’s probably some command that does that without rebooting, but this is just so you keep the possibility in mind.</p>
</aside>
<p>After a lot of fiddling with the Android Bluetooth settings, restarting, and so on, finally:</p>

<figure>
    
    <img class="" src="https://bharadwajraju.com/posts/nothing-ear-2-on-linux/nothing-x-on-android-x86.png" alt="Nothing X running on Android-x86" title="Nothing X running on Android-x86"
    
    
    />
    <figcaption>Look at the pixelation on those images. This app wasn’t built with this <abbr>DPI</abbr> in mind.</figcaption>
</figure>
<p>After setting up developer options to enable Bluetooth <abbr>HCI</abbr> sniffing and <abbr>USB</abbr> debugging, we can <span class="sidenote-ref"><span class="sidenote-ref-text">connect via <abbr>ADB</abbr></span><span class="sidenote-number"><aside class="sidenote" role="note"><!--&#8203;-->&nbsp;<span class="sidenote-surroundings">&nbsp;(</span>See the “using adb with a <abbr>NAT</abbr>’ed <abbr>VM</abbr>” section in <a href="https://www.android-x86.org/documentation/debug.html">the Android-x86 docs</a>. Change the <code>adb localhost:5555</code> command to <code>adb connect localhost:5555</code>.<span class="sidenote-surroundings">)&nbsp;</span></aside></span></span>.</p>

<p>Once we’ve connected <abbr>ADB</abbr>, we can simply select the Android device in Wireshark running on the host, and get our first look at the Bluetooth traffic.</p>

<figure>
    
    <img class="" src="https://bharadwajraju.com/posts/nothing-ear-2-on-linux/wireshark.png" alt="Wireshark" title="Wireshark"
    
    
    />
    <figcaption></figcaption>
</figure>
<p>The whole thing appears to be very chatty; there are nearly 300 events when I do something as simple as changing the <abbr>ANC</abbr> mode. Presumably only some of them are actually relevant, but that is a lot to sift through.</p>
<h2>Protocol Analysis</h2>
<p>One neat trick: if you disconnect and reconnect the earphones while a Wireshark capture is running, the capture logs get a lot more detailed! Compare this to the previous screenshot:</p>

<figure>
    
    <img class="" src="https://bharadwajraju.com/posts/nothing-ear-2-on-linux/wireshark-transparency-cmd.png" alt="Wireshark with more detailed logs" title="Wireshark with more detailed logs"
    
    
    />
    <figcaption></figcaption>
</figure>
<p>All the “Connection oriented channel” packets get turned into actual commands and responses with the correct protocol! Great.</p>

<p>From this we can sift through the packets better. The first thing I tested was switching between having the <abbr>ANC</abbr> off and on transparency mode. If we leave the huge <code>PT=Unknown</code> packets of ~592 bytes alone for a minute, we’ll observe             <small class="sidenote marginnote">&#8203;<span class="sidenote-surroundings">&nbsp;(</span>This observation didn’t come directly. I wasted a lot of time chasing even more dead ends like trying to connect to the Audio Sink service.<span class="sidenote-surroundings">)&nbsp;</span></small> that every time the <abbr>ANC</abbr> is set to transparency, a lot of commands are sent over <abbr>RFCOMM</abbr> to channel 15:</p>


<p>Let’s try manually constructing and sending these packets to the device, back on desktop Linux. I used Wireshark to filter all packets of channel 15 and saved it as <abbr>JSON</abbr>,
then wrote a <a href="https://gitlab.com/-/snippets/3636681">quick Python script</a> to read all the packets and send them out, with a 1 second gap to let me observe the effects. When I ran it, I finally heard that sigh in my ear that signalled transparency mode. This is the packet that does it:</p>
<pre><code>55:60:01:0f:f0:03:00:cb:01:07:00:c5:af</code></pre>
<p>This is clearly the right track. After trying more captured packets, here is the table of commands to do various operations:</p>

<table>
<thead>
<tr>
<th>Command</th>
<th>Packet</th>
</tr>
</thead>
<tbody>
<tr>
<td><abbr>ANC</abbr> Transparency</td>
<td><code>55:60:01:0f:f0:03:00:cb:01:07:00:c5:af</code></td>
</tr>
<tr>
<td><abbr>ANC</abbr> Off</td>
<td><code>55:60:01:0f:f0:03:00:cd:01:05:00:c4:47</code></td>
</tr>
<tr>
<td><abbr>ANC</abbr> High</td>
<td><code>55:60:01:0f:f0:03:00:cf:01:01:00:e6:6f</code></td>
</tr>
<tr>
<td><abbr>ANC</abbr> Mid</td>
<td><code>55:60:01:0f:f0:03:00:d5:01:02:00:e6:9f</code></td>
</tr>
<tr>
<td><abbr>ANC</abbr> Low</td>
<td><code>55:60:01:0f:f0:03:00:d7:01:03:00:e7:0f</code></td>
</tr>
<tr>
<td><abbr>ANC</abbr> Adaptive</td>
<td><code>55:60:01:0f:f0:03:00:dd:01:04:00:e5:3f</code></td>
</tr>
<tr>
<td>In-ear detection Off</td>
<td><code>55:60:01:04:f0:03:00:25:01:01:00:b2:94</code></td>
</tr>
<tr>
<td>In-ear detection On</td>
<td><code>55:60:01:04:f0:03:00:26:01:01:01:73:10</code></td>
</tr>
<tr>
<td>Low-latency mode Off</td>
<td><code>55:60:01:40:f0:02:00:28:02:00:a7:04</code></td>
</tr>
<tr>
<td>Low-latency mode On</td>
<td><code>55:60:01:40:f0:02:00:27:01:00:97:f7</code></td>
</tr>
</tbody>
</table>

<h3>Ear Tip Fit Test</h3>
<p class="unjustifiable">The packet to start an ear tip fit test is <code>55:60:01:14:f0:01:00:2a:01:43:16</code>. We get back some results indicating if the left and right earbuds fit well.</p>

<p>If the left earbud doesn’t fit, but the right does, we get this packet:</p>

<pre><code>0000   55 00 01 0d e0 02 00 00 01 00</code></pre>
<p>If both earbuds fit:</p>
<pre><code>0000   55 00 01 0d e0 02 00 00 00 00</code></pre>
<p>We can make a good guess that the last two bytes represent 0 = good fit, 1 = bad fit, for L/R respectively (further testing confirms this).</p>
<h3>Device Info</h3>
<p class="unjustifiable">If we look at the first few packets exchanged when a connection is made on the Android app, and experiment with sending them, we notice that sending <code>55:60:01:42:c0:00:00:03:e0:d1</code> gives us this reply, which contains the firmware version (<code>1.0.1.101</code>):</p>
<pre><code>0000   55 60 01 42 40 09 00 03 31 2e 30 2e 31 2e 31 30   U`.B@...1.0.1.10
0010   31 fa 36                                          1.6</code></pre>
<p>The version number is 9 characters, so it’s possible the 6th byte <code>09</code> encodes this length information. I guess we’ll test this theory if we ever get a firmware update that changes the number of digits.</p>

<p class="unjustifiable">And if we send <code>55:60:01:06:c0:00:00:05:90:dc</code>, we get this:</p>
<pre><code>0000   55 60 01 06 40 88 00 05 09 32 2c 31 2c 31 2e 30   U`..@....2,1,1.0
0010   2e 31 0a 32 2c 32 2c 31 2e 30 2e 31 2e 31 30 31   .1.2,2,1.0.1.101
0020   0a 32 2c 34 2c 53 48 31 30 31 38 32 33 30 37 30   .2,4,SH101823070
0030   30 32 38 30 37 0a 33 2c 31 2c 31 2e 30 2e 31 0a   02807.3,1,1.0.1.
0040   33 2c 32 2c 31 2e 30 2e 31 2e 31 30 31 0a 33 2c   3,2,1.0.1.101.3,
0050   34 2c 53 48 31 30 31 38 32 33 30 37 30 30 32 38   4,SH101823070028
0060   30 37 0a 34 2c 31 2c 31 2e 30 2e 31 0a 34 2c 32   07.4,1,1.0.1.4,2
0070   2c 31 2e 30 2e 31 2e 31 30 31 0a 34 2c 34 2c 53   ,1.0.1.101.4,4,S
0080   48 31 30 31 38 32 33 30 37 30 30 32 38 30 37 0a   H10182307002807.
0090   91 d1                                             ..</code></pre>
<p>That string starting with <code>SH101</code> is the serial number.</p>

<p class="unjustifiable">If you open an <abbr>RFCOMM</abbr> channel 15 and listen for every incoming packet, you’ll notice that a packet is sent for every “change” to the device. For example, if I change the <abbr>ANC</abbr>, I get a packet starting with <code>55:00:01:03:e0</code>. Now, can I get the <abbr>ANC</abbr> status on demand, without there being a change? For this I looked at more packets sent during early communication, to see which got different responses when I changed the <abbr>ANC</abbr>. I found that when I sent <code>55:60:01:1e:c0:01:00:0c:03:98:19</code>, I get:</p>
<pre><code>0000   55 60 01 1e 40 06 00 0c 01 07 00 02 04 00 10 7a</code></pre>
<p>The 10th byte appears to correspond to the <abbr>ANC</abbr>.            <small class="sidenote marginnote">&#8203;<span class="sidenote-surroundings">&nbsp;(</span>Which, we can notice, is the same as the 10th byte in the command to <em>set</em> <abbr>ANC</abbr>. Turns out we can simplify those seemingly-different <abbr>ANC</abbr> commands to just one with the 10th byte alone differing. The other variations were unrelated.<span class="sidenote-surroundings">)&nbsp;</span></small></p>


<table>
<thead>
<tr>
<th> Byte </th>
<th> ANC Mode </th>
</tr>
</thead>
<tbody>
<tr>
<td> 7    </td>
<td> Transparency </td>
</tr>
<tr>
<td> 5    </td>
<td> Off </td>
</tr>
<tr>
<td> 4    </td>
<td> Adaptive </td>
</tr>
<tr>
<td> 3    </td>
<td> Low </td>
</tr>
<tr>
<td> 2    </td>
<td> Mid </td>
</tr>
<tr>
<td> 1    </td>
<td> High</td>
</tr>
</tbody>
</table>

<h3>Shortcuts</h3>
<p>The Ear (2) has a number of different pinch/hold controls.</p>

<p class="unjustifiable">Whenever we click on the controls for the left earbud, the app sends out <code>55:60:01:18:c0:00:00:51:39:21</code> and receives, for example, this in response:</p>
<pre><code>0000   55 60 01 18 40 21 00 51 08 02 01 02 08 03 01 02
0010   09 02 01 03 08 03 01 03 08 02 01 07 16 03 01 07
0020   16 02 01 09 01 03 01 09 01 e0 30</code></pre>
<p>If we change one shortcut (set left double-pinch to skip forward), we get:</p>
<pre><code>0000   55 60 01 18 40 21 00 <ins>1d</ins> 08 02 01 02 <ins>09</ins> 03 01 02
0010   09 02 01 03 08 03 01 03 08 02 01 07 16 03 01 07
0020   16 02 01 09 01 03 01 09 01 <ins>39</ins> <ins>6d</ins></code></pre>
<p>We see changes on bytes 8 and 13 (<code>08</code> → <code>09</code>), and in the last two bytes. If we change left double-pinch to voice assistant, we get:</p>
<pre><code>0000   55 60 01 18 40 21 00 1d 08 02 01 02 <ins>0b</ins> 03 01 02
0010   09 02 01 03 08 03 01 03 08 02 01 07 16 03 01 07
0020   16 02 01 09 01 03 01 09 01 b9 6e</code></pre>

<p>Compared to last time, only byte 13 is changed (to <code>0b</code>). I guess byte 13 represents the actual action.</p>

<p>Let’s try setting triple pinch to skip forward:</p>
<pre><code>0000   55 60 01 18 40 21 00 1d 08 02 01 02 0b 03 01 02
0010   09 02 01 03 09 03 01 03 08 02 01 07 16 03 01 07
0020   16 02 01 09 01 03 01 09 01 <ins>29</ins> bc</code></pre>
<p>This time the 21st byte is changed. If I set triple pinch to voice assistant, that 21st byte changes to <code>0b</code>.</p>

This is the table of shortcuts and byte positions:
<table>
<thead>
<tr>
<th> Shortcut </th>
<th> Byte index (from 0) </th>
</tr>
</thead>
<tbody>
<tr>
<td> Left double-pinch </td>
<td> 12 </td>
</tr>
<tr>
<td> Left triple-pinch </td>
<td> 20 </td>
</tr>
<tr>
<td> Left pinch-hold </td>
<td> 28 </td>
</tr>
<tr>
<td> Left double-pinch-hold </td>
<td> 36 </td>
</tr>
<tr>
<td> Right double-pinch </td>
<td> 16 </td>
</tr>
<tr>
<td> Right triple-pinch </td>
<td> 24 </td>
</tr>
<tr>
<td> Right pinch-hold </td>
<td> 32 </td>
</tr>
<tr>
<td> Right double-pinch-hold </td>
<td> 40 </td>
</tr>
</tbody>
</table>
<p>And of shortcut actions and their encodings:</p>
<table>
<thead>
<tr>
<th> Action </th>
<th> Encoding </th>
</tr>
</thead>
<tbody>
<tr>
<td> Skip Back </td>
<td> <code>0x08</code> </td>
</tr>
<tr>
<td> Skip Forward </td>
<td> <code>0x09</code> </td>
</tr>
<tr>
<td> Voice Assistant </td>
<td> <code>0x0b</code> </td>
</tr>
<tr>
<td> Volume Up </td>
<td> <code>0x12</code> </td>
</tr>
<tr>
<td> Volume Down </td>
<td> <code>0x13</code> </td>
</tr>
<tr>
<td> Noise Control (Active/Off) </td>
<td> <code>0x14</code> </td>
</tr>
<tr>
<td> Noise Control (Transparency/Active) </td>
<td> <code>0x16</code> </td>
</tr>
<tr>
<td> Noise Control (Transparency/Active/Off) </td>
<td> <code>0xa</code> </td>
</tr>
<tr>
<td> Noise Control (Transparency/Off) </td>
<td> <code>0x15</code> </td>
</tr>
<tr>
<td> No Action </td>
<td> <code>0x01</code> </td>
</tr>
</tbody>
</table>
<p>Looking at the commands to change shortcuts now (this is to set left triple-pinch to skip forward):</p>
<pre><code>0000   55 60 01 03 f0 05 00 59 01 02 01 03 09 de 71</code></pre>
<p>I noticed that the 13th byte encodes the shortcut action, and the previous two bytes match the previous two bytes before the index of that shortcut in the shortcut-info response as we figured out earlier. Now we can set the shortcut for any action.</p>

<p>And what’s really cool is that like this we can use configurations that the official Nothing X app doesn’t allow, yet are possible on the device. For example, the app restricts double-pinch to skip forward/back and voice assistant. But now I can make double-pinch do noise control or volume up/down as well.</p>


<h2>The Finished Product</h2>
<p>You can get the <abbr>CLI</abbr> tool here: <a href="https://gitlab.com/bharadwaj-raju/ear2ctl"><strong>bharadwaj-raju / ear2ctl</strong>.</a></p>
<pre><code><span class=gp>$ </span>ear2ctl<span class=w> </span>info
<span class=go>Address: 2C:BE:EB:69:20:5A</span>
<span class=go>Firmware version: 1.0.1.101</span>
<span class=go>Serial number: SH10182307002807</span>

<span class=gp>$ </span>ear2ctl<span class=w> </span>anc
<span class=go>ANC: off</span>

<span class=gp>$ </span>ear2ctl<span class=w> </span>anc<span class=w> </span>transparency

<span class=gp>$ </span>ear2ctl<span class=w> </span>anc
<span class=go>ANC: transparency</span>

<span class=gp>$ </span>ear2ctl<span class=w> </span>shortcuts
<span class=go>left:</span>
<span class=go>        double-pinch: skip-forward</span>
<span class=go>        triple-pinch: skip-back</span>
<span class=go>        pinch-hold: noise-control-transparency-active-off</span>
<span class=go>        double-pinch-hold: voice-assistant</span>
<span class=go>right:</span>
<span class=go>        double-pinch: skip-forward</span>
<span class=go>        triple-pinch: skip-back</span>
<span class=go>        pinch-hold: voice-assistant</span>
<span class=go>        double-pinch-hold: noise-control-transparency-off</span>

<span class=gp>$ </span>ear2ctl<span class=w> </span>shortcuts<span class=w> </span>right<span class=w> </span>double-pinch-hold<span class=w> </span>no-action

<span class=gp>$ </span>ear2ctl<span class=w> </span>--help
<span class=go>Controls for the Nothing Ear (2)</span>

<span class=go>Usage: ear2ctl <command></span>

<span class=go>Commands:</span>
<span class=go>  info              Device information</span>
<span class=go>  shortcuts         View and set pinch gestures</span>
<span class=go>  anc               Control active noise cancellation</span>
<span class=go>  ear-tip-fit-test  Start or stop the ear tip fit test</span>
<span class=go>  in-ear-detection  Turn in-ear detection on or off</span>
<span class=go>  low-latency-mode  Turn low latency mode on or off</span>
<span class=go>  help              Print this message or the help of the given subcommand(s)</span>

<span class=go>Options:</span>
<span class=go>  -h, --help     Print help</span>
<span class=go>  -V, --version  Print version</span>
</code></pre> ]]></content>
    <link href="https://bharadwajraju.com/posts/nothing-ear-2-on-linux"/>
    <published>2024-01-06T00:00:00+05:30</published>
  </entry>
  <entry>
    <id>https://bharadwajraju.com/posts/stacked-sines</id>
    <title>Stacked Sines</title>
    <updated>2025-09-13T00:00:00+05:30</updated>
    <content type="CDATA"><![CDATA[
<figure>
    
    <img class="" src="https://bharadwajraju.com/posts/stacked-sines/sterk_hvalros_you_are_not_a_robot_5d5AmwolYpo_2.jpg" alt="You are not a robot, by Sterk Hvalros." title="You are not a robot, by Sterk Hvalros."
    
    height="500"
    />
    <figcaption><a href="//youtu.be/5d5AmwolYpo"><em>"You are not a robot"</em></a></figcaption>
</figure>
<p><a href="https://www.youtube.com/@SterkHvalros">Sterk Hvalros</a> creates a lot of art around the ideas of ripples and waves, and documents his process of making these pieces.</p>


<p>Watching them, I wanted to experiment with some procedural generation along those lines.</p>

<p>I thought about and tried some ways of representing and propagating perturbations like in the video, and my eventual idea was to generate pieces of sine waves for each horizontal line, and to “dampen” them to generate their ripple effects on all other lines.</p>

<p>Sine waves make this pretty nice, mathematically. If you have a function of:</p>
<pre><code>y(x) = amplitude * sin((x - start) / width)</code></pre>
<p>you can vary the <code>amplitude</code> and <code>width</code> parameters to control the size.</p>
<p>Sorry, this live demo cannot be displayed here. Please visit the original site.</p>
<p>Then, the idea is that you generate a set of such sine perturbations at random lines and at random starting positions, and add their effect (<code>y(x)</code>) to each line, dampened by how far the line being rendered is from the origin line of that perturbation.</p>

<p>For each stage of dampening, reduce the amplitude by a constant, increase the width by 2 units, and decrease the start position by π units.            <small class="sidenote marginnote">&#8203;<span class="sidenote-surroundings">&nbsp;(</span>The choice to increase width by 2 units is arbitrary.<span class="sidenote-surroundings">)&nbsp;</span></small>            <small class="sidenote marginnote">&#8203;<span class="sidenote-surroundings">&nbsp;(</span>Still, whatever our choice for the increase in width, we can calculate the corresponding decrease in starting coordinate which will keep the peaks aligned by equating <code>(x-s)/w</code> and <code>(x-s')/w'</code> both to <code>π/2</code>.<span class="sidenote-surroundings">)&nbsp;</span></small>That lets it spread out while still being on-center.</p>
<p>Sorry, this live demo cannot be displayed here. Please visit the original site.</p>
<p>Thus far this looks simplistic, but when adding multiple randomly-generated sines to one image, the effect is cool.</p>
<p>Sorry, this live demo cannot be displayed here. Please visit the original site.</p>]]></content>
    <link href="https://bharadwajraju.com/posts/stacked-sines"/>
    <published>2025-09-13T00:00:00+05:30</published>
  </entry>
  <entry>
    <id>https://bharadwajraju.com/posts/angled-random-walk-mountains</id>
    <title>Angled Random Walks for DLA-like Terrain Generation</title>
    <updated>2024-08-03T00:00:00+05:30</updated>
    <content type="CDATA"><![CDATA[
<p>I watched this excellent video, <a href="https://www.youtube.com/watch?v=gsJHzBTPG0Y"><em>Better Mountain Generators That Aren’t Perlin Noise or Erosion</em>]</a> by <a href="https://www.youtube.com/@JoshsHandle">Josh’s Channel</a>. It discusses generating mountain heightmaps
using a technique called <a href="https://en.wikipedia.org/wiki/Diffusion-limited_aggregation">Diffusion-Limited Aggregation</a>. The structures produced by this process are known as Brownian trees.</p>

<figure>
    
    <img class="" src="https://bharadwajraju.com/posts/angled-random-walk-mountains/Brownian_tree.gif" alt="Brownian tree" title="Brownian tree"
    
    
    />
    <figcaption>
<p>Growing Brownian tree.</p>

<p>Animation by <a href="https://commons.wikimedia.org/wiki/File:Brownian_tree.gif">あるうぃんす</a>.</p>
</figcaption>
</figure>
<p>In <abbr>DLA</abbr>, we start with a seed typically placed at the center. At each step, we randomly place points on the grid and then random-walk them until they hit an existing particle, at which point they are frozen there. This is, of course, very inefficient. The video describes a good technique to get it to be faster, by starting with a small grid and doing a <span class="sidenote-ref"><span class="sidenote-ref-text">crisp upscale</span><span class="sidenote-number"><aside class="sidenote" role="note"><!--&#8203;-->&nbsp;<span class="sidenote-surroundings">&nbsp;(</span>This upscale isn't done directly on the pixels — instead, we keep track of which pixel sticks to which, and use that graph to populate a larger grid.<span class="sidenote-surroundings">)&nbsp;</span></aside></span></span> after the grid is filled to a certain degree, and repeating the process until we get to the desired size.</p>

<p>But my immediate thoughts after the video were that surely this would be faster the other way round — by generating outwards from the initial seed. After experimenting with a lot of approaches, I found a way that yields <em>fairly</em> <abbr>DLA</abbr>-like results with much less computational cost.</p>
<h2>Approach</h2>
<p>We have a number of <em>random walkers</em> on the grid. Each of them has these properties:</p>
<ol>  <li><em>Age</em>: How many pixels it travelled since it was spawned.</li>  <li><em>Generation</em>: How many parent walkers it has.</li>  <li><em>Angle</em>: What angle the walker aims towards.</li>  <li><em>Type</em>: Is it a <em>long</em> or <em>short</em> walker. <em>Short</em> walkers don’t split into more walkers when they end.</li></ol>
<p>The algorithm for generation is:</p>
<ol>    <li>Start with a grid of zeros.</li>    <li>Place a number of walkers at the centre, all aimed at different angles.</li>    <li>        While there are any walkers:
        <ul>          <li>If it is a long walker and its age module some frequency parameter is zero, spawn a short walker at that position.</li>          <li>If its age is greater than some maximum age, it dies, and…
            <ul>                <li>If its generation is less than some maximum generation, and it is a long walker, spawn some number of long walkers where it stopped, each aimed slightly offset from the parent's angle.</li>            </ul>          </li>            
          <li>Else, the walker moves in a random direction, chosen via weighted sampling where the weights are smaller the larger the angular distance between that direction and the target angle, and the <span class="sidenote-ref"><span class="sidenote-ref-text">most opposite direction is removed</span><span class="sidenote-number"><aside class="sidenote" role="note"><!--&#8203;-->&nbsp;<span class="sidenote-surroundings">&nbsp;(</span>Otherwise, the walkers wind back on themselves and fail to spread apart sufficiently. An example of what that looks like: 
<figure>
    
    <img class="" src="https://bharadwajraju.com/posts/angled-random-walk-mountains/with-least-likely-included.png" alt="with least likely included, the generated shape is a lot smaller and more bloblike" title="with least likely included, the generated shape is a lot smaller and more bloblike"
    
    
    />
    <figcaption></figcaption>
</figure><span class="sidenote-surroundings">)&nbsp;</span></aside></span></span> by subtracting its weight from every weight. The point it moves to is filled in on the grid.</li>        </ul>    </li></ol><h2>Implementation</h2>

<p>I've written a Rust implementation of this algorithm.</p>
<ul>  <li><em>Repository</em>: <a href="https://github.com/bharadwaj-raju/angled-random-walker">bharadwaj-raju / angled-random-walker</a></li>  <li><em>Crates.io</em>: <a href="https://crates.io/crates/angled-random-walker">angled-random-walker</a></li>  <li><em>Documentation</em>: <a href="https://docs.rs/angled-random-walker/latest/angled_random_walker/">angled_random_walker on docs.rs</a></li></ul><h2>Heightmap</h2>
<p>By filling each walked pixel with the cumulative age of its walker, and blurring the result, we get a simple heightmap. But this just gives you mountain-like smooth blobs.</p>

<p>To get more interesting terrain, I superimpose a clamped and lightly-blurred version. This preserves the smaller and sharper details generated in the process. The effect is — in my estimation — close to the sought-after erosion look.</p>
<h2>Demonstration</h2><p>Sorry, this live demo cannot be displayed here. Please visit the original site.</p><h2>Similar Stuff</h2>
<p><a href="https://web.archive.org/web/20240728100625if_/https://planet11games.com/">Planet Eleven Games</a> posted about <a href="https://old.reddit.com/r/proceduralgeneration/comments/1bup6wm/using_drunken_walk_for_height_maps/"><em>Using drunken walk for height maps</em></a>. They were inspired by the exact same video, but the approach they use is different, involving an unbiased random expansion with each new pixel having a chance of dropping in height. Check out their <a href="https://web.archive.org/web/20240415145101if_/https://planet11games.com/drunkwalk/">demo</a>.</p>
]]></content>
    <link href="https://bharadwajraju.com/posts/angled-random-walk-mountains"/>
    <published>2024-08-03T00:00:00+05:30</published>
  </entry>
</feed>
