Skip to content

Commit a0849c6

Browse files
committed
Refactor volume table handling and Bluetooth connection logic
- Updated `uninstall_cinderhome.sh` to remove specific Cinder directories during uninstallation. - Modified `fake_pst.cpp` to change Bluetooth address format in connection requests for consistency. - Enhanced `cinder-voltable.c` to implement a more robust method for installing volume tables from predefined directories, ensuring proper checks for Sony's files. - Adjusted `main.cpp` to reflect updated Bluetooth connection request logging. - Improved `test_launcher.sh` to verify the presence of volume tables and log appropriate messages based on their availability. - Expanded documentation to clarify the process of obtaining and using volume curve tables, emphasizing the need for user-provided files. - Added a new raw HCI snoop capture file for Bluetooth analysis, ensuring sensitive data is redacted.
1 parent 7860396 commit a0849c6

20 files changed

Lines changed: 269 additions & 56 deletions

.github/release-notes.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ On Windows:
102102
Get-FileHash .\cinder-installer-windows-x64.exe -Algorithm SHA256
103103
```
104104

105+
Every file above also has a GitHub build attestation. With the [GitHub CLI](https://cli.github.com):
106+
107+
```
108+
gh attestation verify cinder-installer-windows-x64.exe -R superwilso/Cinder
109+
```
110+
111+
A pass means the file was built by this repository's release workflow from the tagged commit, and
112+
is signed in a way an uploaded replacement cannot fake. The checksums say the file is intact; the
113+
attestation says where it came from.
114+
105115
The installer is unsigned, so SmartScreen will warn about an unknown publisher — that
106116
is expected for an unsigned binary and is not itself evidence of anything. Check the
107117
hash if you want more than my word for it.

.github/workflows/release.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ jobs:
124124
needs: [build, build-linux]
125125
runs-on: ubuntu-latest
126126
if: startsWith(github.ref, 'refs/tags/')
127+
# Job-level permissions REPLACE the workflow's, so contents: write is restated. id-token lets
128+
# the job ask GitHub's OIDC issuer for the short-lived identity Sigstore signs with;
129+
# attestations lets it store the result against this repository.
130+
permissions:
131+
contents: write
132+
id-token: write
133+
attestations: write
127134
steps:
128135
- uses: actions/checkout@v7
129136

@@ -140,6 +147,20 @@ jobs:
140147
# A published binary nobody can verify is a binary nobody should run.
141148
( cd dist && sha256sum -- * | tee SHA256SUMS )
142149
150+
# BUILD PROVENANCE (docs/SHORTCOMINGS.md D7). SHA256SUMS proves a download matches what this
151+
# job published; it cannot prove this workflow, from this commit, produced it — anyone able to
152+
# replace a file could replace the sums beside it. An attestation is signed through Sigstore
153+
# with the run's own OIDC identity (repository, workflow file, commit), so it can be checked
154+
# with `gh attestation verify <file> -R superwilso/Cinder` and cannot be minted outside a run.
155+
#
156+
# One step over SHA256SUMS rather than one per build job: every published file, the two
157+
# .UPGs included, is covered by the same list the release body inlines. It says nothing about
158+
# where the committed ARM payload came from — that is D4's manifest, checked above.
159+
- name: Attest build provenance for every published file
160+
uses: actions/attest-build-provenance@v4
161+
with:
162+
subject-checksums: dist/SHA256SUMS
163+
143164
# The sums are INLINED INTO THE RELEASE PAGE, not merely attached. Verifying a download
144165
# against a checksum file fetched from beside it is close to circular: whoever could swap
145166
# one could swap the other. The release body is the rendered record of what the workflow

CHANGELOG.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,10 @@ level the commit history supports; from `v0.1.6` onward, entries are written as
109109
confirm. The claim came from a reference device an earlier session had copied the files onto.
110110
Once they were gone, every boot logged `volume curve: cinder-voltable wm1a FAILED — stock curve
111111
stays` — to logcat, where nobody would see it — and the player quietly kept the stock curve.
112-
Cinder does not ship Sony's files, so on a stock player those two choices still do nothing — but
113-
now visibly. The installer's description says so; the install log warns when the chosen table is
114-
missing; and at boot `cinderhome.log` says `volume curve: 'wm1a' needs …/ov_127x.tbl, which is not
115-
part of the stock firmware and is not on this player — stock curve stays` instead of a bare
116-
FAILED in logcat.
112+
Cinder cannot ship Sony's files, so the two choices now work from a table the user brings (see
113+
Added) and say so when there is none: the install log warns, and at boot `cinderhome.log` says
114+
`volume curve: 'wm1a' needs Sony's ov_127x.tbl, which this player's firmware does not include and
115+
no install has supplied — stock curve stays` instead of a bare FAILED in logcat.
117116

118117
- **Withdrawn: "there is no EU volume cap".** `analysis/RE_volume_tables.md` (2026-09-04) compared
119118
the two region tables by sweeping the codec's analogue attenuator and found identical curves.
@@ -154,6 +153,29 @@ level the commit history supports; from `v0.1.6` onward, entries are written as
154153
and SensMe. The README and the installer no longer call the `signature` component the whole of
155154
Walkman One's sound signature: it reproduces the plus modes, and the external tunings are another
156155
model's firmware.
156+
- **The `wm1a` and `w1` volume curves work from a table you bring.** *Launcher sandbox and host
157+
checks; device-unverified.* Put Sony's `ov_127x.tbl` (or `ov_1280.tbl`) at the top of the drive,
158+
or have Wampy installed, which already keeps the same files on the player. The installer takes a
159+
copy only if its SHA-256 is exactly Sony's, re-checks it after copying it into `/system`, and
160+
says in the install log which file it used or why it refused one. The setuid helper now looks in
161+
Sony's directory and then Cinder's, and reads nothing else. Uninstall removes Cinder's copy.
162+
`install.md`*The volume curve tables* says where to get them and why Cinder cannot include
163+
them: they are Sony's files, and Cinder's MIT licence cannot cover them.
164+
- **Release downloads carry a GitHub build attestation.** *Workflow change; runs for the first time
165+
on the next tag.* `gh attestation verify <file> -R superwilso/Cinder` shows each installer and
166+
`.UPG` was built by this repository's release workflow from the tagged commit. The release notes
167+
and `SECURITY.md` explain how to check (`docs/SHORTCOMINGS.md` D7).
168+
169+
### Security and privacy
170+
171+
- **`SECURITY.md` said Cinder ships twelve setuid-root helpers. It ships eight**, and the list left
172+
out `cinder-battery`. It also still described the installer as driving Sony's updater, which it no
173+
longer does. `docs/SHORTCOMINGS.md` repeated the same wrong count.
174+
- **Two real Bluetooth addresses were still in the tree** after the 2026-08-24 redaction: 84 times
175+
inside the committed HCI capture, and in the retry-mode measurement quoted in `main.cpp`, the
176+
host harness and the 2026-08-26 Bluetooth audit. Both now use the documentation range, and the
177+
capture is `hci_reconnect_20260819_redacted.btsnoop`. The original capture is added to
178+
`tools/rewrite_history.sh`; the addresses stay in older commits until that rewrite is pushed.
157179

158180
## [0.3.3] — 2026-09-12
159181

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,11 @@ repository:
380380
Three settings changed the signal: regions CEW2 and KR3, which load Sony's quieter volume table,
381381
and gain mode 1. The plus modes, the sound signatures and DAC mode measured the same as stock.
382382
- **Can Cinder do the same without flashing?** The plus modes, yes — the
383-
[`signature` component](install.md#the-sound-signature). The other models' volume tables, not
384-
on a stock player: they are not part of the NW-A50's firmware and Cinder does not ship Sony's
385-
files, so the installer's `wm1a` and `w1` volume curves only take effect on a player that
386-
already holds those tables. The external tunings, no.
383+
[`signature` component](install.md#the-sound-signature). The NW-WM1A's volume curve, yes, once you
384+
bring the table: it is not part of the NW-A50's firmware and Cinder cannot include Sony's files,
385+
so the installer uses a copy you put on the player's drive, or the one Wampy already installed,
386+
and only if it is byte-for-byte Sony's — see
387+
[The volume curve tables](install.md#the-volume-curve-tables). The external tunings, no.
387388
- **Can I put Cinder on top of Walkman One?** Untested — see [Known limitations](#known-limitations).
388389
- **What about my region's volume limit?** If your player was sold where Sony restricts volume,
389390
Cinder leaves the restriction in place unless you choose a different volume curve, which replaces

SECURITY.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Security policy
22

3-
Cinder replaces the Home application on a Sony NW-A50-series Walkman. It ships twelve setuid-root
4-
helpers, an installer that drives Sony's own firmware updater, and a launcher that runs on the boot
5-
path. **This device has no public DFU or EDL recovery path**: a bad boot is recovered by the
3+
Cinder replaces the Home application on a Sony NW-A50-series Walkman. It ships eight setuid-root
4+
helpers, an installer that triggers the player's own firmware updater, and a launcher that runs on
5+
the boot path. **This device has no public DFU or EDL recovery path**: a bad boot is recovered by the
66
escape ladder described below, or by an eMMC restore, or not at all.
77

88
That is the context for everything here. Please read it before reporting, and before contributing
@@ -23,15 +23,17 @@ no SLA, and there is no bug bounty.
2323

2424
The parts where a defect can cost someone their device or their root:
2525

26-
* **The setuid helpers** (`cinder-power`, `cinder-msc`, `cinder-clock`, `cinder-fm`,
27-
`cinder-voltable`, `cinder-gpunode`, `cinder-umount`, …). They run as root on behalf of an
26+
* **The setuid helpers** — all eight: `cinder-power`, `cinder-msc`, `cinder-clock`, `cinder-fm`,
27+
`cinder-voltable`, `cinder-battery`, `cinder-gpunode`, `cinder-umount` (the `chmod 4755` lines
28+
in `install_cinderhome.sh` are the authoritative list). They run as root on behalf of an
2829
unprivileged UI. Argument handling, path handling and anything reachable from `/contents`
2930
(which is FAT, world-writable, and shared with any PC the player is plugged into) matter most.
3031
* **The launcher and the escape ladder**`install_cinderhome.sh`'s launcher, the bad-boot
3132
counter, the auto-revert, the crash supervisor, the kill switch. A defect that disarms an escape
3233
is more serious than one that crashes the app, because the app crashing is what the escapes are
3334
for.
34-
* **The installer** — it writes `NW_WM_FW.UPG` to a device root and triggers Sony's updater.
35+
* **The installer** — it writes `NW_WM_FW.UPG` to a device root and sends the player's vendor
36+
SCSI upgrade command (pass-through on Windows, `SG_IO` on Linux), which runs as administrator/root.
3537
* **Anything that can make the device unbootable**, whether or not an attacker is involved.
3638

3739
## What is out of scope
@@ -68,5 +70,16 @@ sha256sum -c SHA256SUMS
6870
```
6971

7072
`SHA256SUMS` is attached to each release. It proves the download matches what the release workflow
71-
produced. It does **not** prove the workflow built this source tree — there is no build
72-
attestation yet (D7).
73+
produced. On its own it does **not** prove the workflow built this source tree.
74+
75+
Releases tagged after 2026-09-14 also carry a GitHub build attestation for every published file:
76+
77+
```sh
78+
gh attestation verify cinder-installer-windows-x64.exe -R superwilso/Cinder
79+
```
80+
81+
A pass means the file was produced by `.github/workflows/release.yml` in this repository, from the
82+
commit the tag points at — signed through Sigstore, so it cannot be forged by whoever uploads a
83+
file. What it does not cover (D7): the installer is still unsigned for SmartScreen, and the ARM
84+
payload inside it is committed rather than built on the runner, so for those bytes the attestation
85+
leans on `tools/release.sh`'s rebuild-and-compare manifest (D4).

analysis/G_bt_nfc/RE_findings.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
> Nothing here depends on their value: what the findings turn on is that an address is *present*,
88
> that it is six bytes, and which device it belongs to — and the device is named alongside it.
99
> The same substitution was applied across `cinder-home/src/`, `STATUS.md` and `docs/`.
10+
> **2026-09-14:** two more were found and replaced the same way — `:03`, the peer in the 2026-08-26
11+
> retry-mode measurement (`main.cpp`, `fake_pst.cpp`, `AUDIT_2026-08-26_bluetooth.md`), and the
12+
> CMF Buds' raw address inside the committed HCI capture, now
13+
> `hci_reconnect_20260819_redacted.btsnoop` (`:02`, byte-for-byte otherwise).
1014
1115
**Status: the API surface is fully recovered. No Ghidra needed.**
1216

-6.77 KB
Binary file not shown.
6.77 KB
Binary file not shown.

cinder-home/deploy/components.conf

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ voltable | CINDER_VOLTABLE | enum:stock,wm1a,w1 | stock | Wired volume curve
7878
all — and its live steps coarsen toward the top, which is where the volume-change pop is
7979
loudest. `wm1a` is the NW-WM1A's own table: monotonic across the whole range, no dead zones,
8080
finer steps at the top.
81-
NOT ON A STOCK PLAYER. Neither `wm1a` (ov_127x.tbl) nor `w1` (ov_1280.tbl) is part of Sony's
82-
NW-A50 firmware, and Cinder does not ship Sony's files, so on a stock player both leave the
83-
stock curve in place (checked against a stock player's /system/usr/share/audio_dac,
84-
2026-09-13). They only take effect where those tables have already been put there.
81+
BRING THE TABLE. Neither `wm1a` (ov_127x.tbl) nor `w1` (ov_1280.tbl) is part of Sony's NW-A50
82+
firmware, and Cinder cannot include them: they are Sony's files. Before installing, put
83+
ov_127x.tbl (and ov_dsd_127x.tbl, for DSD) at the top of the player's drive, next to MUSIC —
84+
install.md "The volume curve tables" says where to get them. If Wampy is installed, its copies
85+
are used and nothing needs downloading. A file is taken only if its SHA-256 is Sony's own;
86+
without one the stock curve stays and the install log says which file is missing.
8587
On this (European) unit, with the table present, it does NOT raise the maximum — both curves reach the same ceiling —
8688
but it IS quieter at the same number through the mid range, so a familiar volume setting will
8789
sound different.

cinder-home/deploy/install_cinderhome.sh

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -315,16 +315,62 @@ if [ ! -f /contents/cinder_voltable.conf ]; then
315315
else
316316
echo "volume curve: keeping existing /contents/cinder_voltable.conf ($("$BB" cat /contents/cinder_voltable.conf 2>/dev/null))"
317317
fi
318-
# The two non-stock curves need tables a stock NW-A50 does not have (only its own 1291 set ships,
319-
# and Cinder does not ship Sony's files). Say so here, where the choice lands, as well as at boot.
318+
# 1f3b) the tables `wm1a` and `w1` need. A stock NW-A50 has only its own 1291 set, and Cinder does
319+
# not ship Sony's files: they are Sony's copyright, not Cinder's to license (install.md, "The
320+
# volume curve tables"). So they come from where the user already has them —
321+
# /contents/<name> copied to the top of the player's drive over USB
322+
# Wampy's sound_settings the same bytes, named for the model Wampy took them from
323+
# — and are installed ONLY when they are byte-for-byte Sony's file. This is the step where a
324+
# file from the user-writable drive enters root-owned /system, and cinder-voltable writes what
325+
# it finds there straight into the codec, so the hash is checked again on the copy that lands.
326+
# Every install looks, whatever curve is chosen, so the curve can be changed later over USB
327+
# without a reinstall; a table installed earlier stays when no candidate is found.
328+
# VT_DIR must match CINDER_DIR in src/cinder-voltable.c and the launcher's check below.
329+
VT_DIR=/system/vendor/unknown321/usr/share/cinder/audio_dac
330+
WAMPY_SS=/system/vendor/unknown321/usr/share/wampy/sound_settings
331+
vt_sha() { "$BB" sha256sum "$1" 2>/dev/null | "$BB" cut -d' ' -f1; }
332+
vt_take() { # vt_take <name> <sha256> <candidate>... — install the first candidate that IS Sony's <name>
333+
vt_name="$1"; vt_want="$2"; shift 2
334+
for vt_cand in "$@"; do
335+
[ -f "$vt_cand" ] || continue
336+
vt_got="$(vt_sha "$vt_cand")"
337+
if [ "$vt_got" != "$vt_want" ]; then
338+
# Wampy's folders hold other tables too; only a file put on the drive is worth a word.
339+
case "$vt_cand" in
340+
/contents/*) echo "WARN: $vt_cand is not Sony's $vt_name (SHA-256 ${vt_got:-unreadable}) — not installed" ;;
341+
esac
342+
continue
343+
fi
344+
"$BB" mkdir -p "$VT_DIR" 2>/dev/null
345+
"$BB" cat "$vt_cand" > "$VT_DIR/$vt_name.tmp" 2>/dev/null
346+
if [ "$(vt_sha "$VT_DIR/$vt_name.tmp")" = "$vt_want" ]; then
347+
"$BB" chmod 644 "$VT_DIR/$vt_name.tmp"
348+
"$BB" mv -f "$VT_DIR/$vt_name.tmp" "$VT_DIR/$vt_name"
349+
echo "volume table: installed $vt_name from $vt_cand (SHA-256 matches Sony's)"
350+
return 0
351+
fi
352+
"$BB" rm -f "$VT_DIR/$vt_name.tmp" 2>/dev/null
353+
echo "WARN: copying $vt_cand to $VT_DIR did not verify — not installed"
354+
done
355+
return 1
356+
}
357+
# The hashes are of Sony's own files: the NW-WM1A's ov_127x / ov_dsd_127x (Wampy's ZX-300 1288
358+
# pair is the same bytes) and Walkman One's ov_1280 (Wampy's ov_1280_nw-wm1a).
359+
vt_take ov_127x.tbl b5dd878b0484c43312f3a93c6675d40d546f78ef7494684991eea0aef8651320 \
360+
/contents/ov_127x.tbl "$WAMPY_SS"/master_volume/*.tbl
361+
vt_take ov_dsd_127x.tbl b777b7e2786f952797e82830d5d17565d03a4758555436a376bf6daff5e50ac7 \
362+
/contents/ov_dsd_127x.tbl "$WAMPY_SS"/master_volume_dsd/*.tbl
363+
vt_take ov_1280.tbl 0902981b0b00b5f98eb04d00713f4ddc96f4bb91af76289d8cce15486c30a9c7 \
364+
/contents/ov_1280.tbl "$WAMPY_SS"/master_volume/*.tbl
365+
# Say so here, where the choice lands, as well as at boot, when the chosen curve still has no table.
320366
vt_now="$("$BB" cat /contents/cinder_voltable.conf 2>/dev/null | "$BB" tr -d ' \t\r\n')"
321367
case "$vt_now" in
322368
wm1a) vt_tbl=ov_127x.tbl ;;
323369
w1) vt_tbl=ov_1280.tbl ;;
324370
*) vt_tbl="" ;;
325371
esac
326-
if [ -n "$vt_tbl" ] && [ ! -f "/system/usr/share/audio_dac/$vt_tbl" ]; then
327-
echo "WARN: volume curve '$vt_now' needs /system/usr/share/audio_dac/$vt_tbl, which this player does not have (it is not part of the stock firmware) — the stock curve stays."
372+
if [ -n "$vt_tbl" ] && [ ! -f "/system/usr/share/audio_dac/$vt_tbl" ] && [ ! -f "$VT_DIR/$vt_tbl" ]; then
373+
echo "WARN: volume curve '$vt_now' needs Sony's $vt_tbl, which this player's firmware does not include and which was not found at the top of the drive or in Wampy — the stock curve stays. Put $vt_tbl on the drive and install again (install.md: The volume curve tables)."
328374
fi
329375

330376
# 1f4) battery/charger reader. The bq24262 charger's registers live under /proc/regmon/bq24262/,
@@ -702,12 +748,13 @@ if [ -x "$VOLTABLE_BIN" ] && [ -f "$VOLTABLE_CONF" ]; then
702748
vt_log "volume curve: stock — keeping the table the boot script loaded"
703749
;;
704750
wm1a|w1)
705-
# Both need a table a STOCK NW-A50 DOES NOT HAVE: only its own 1291 set ships, and
706-
# Cinder does not ship Sony's files. Look first, so the log says why instead of a bare
707-
# FAILED — the helper's rc 4 means the same thing, silently.
751+
# Both need a table a STOCK NW-A50 DOES NOT HAVE: only its own 1291 set ships. An
752+
# install puts the user's own copy in Cinder's directory once its SHA-256 matches Sony's
753+
# (step 1f3b above), and cinder-voltable looks in both places. Look first, so the log
754+
# says why instead of a bare FAILED — the helper's rc 4 means the same thing, silently.
708755
case "$vt" in wm1a) vt_tbl=ov_127x.tbl ;; *) vt_tbl=ov_1280.tbl ;; esac
709-
if [ ! -f "/system/usr/share/audio_dac/$vt_tbl" ]; then
710-
vt_log "volume curve: '$vt' needs /system/usr/share/audio_dac/$vt_tbl, which is not part of the stock firmware and is not on this player — stock curve stays"
756+
if [ ! -f "/system/usr/share/audio_dac/$vt_tbl" ] && [ ! -f "/system/vendor/unknown321/usr/share/cinder/audio_dac/$vt_tbl" ]; then
757+
vt_log "volume curve: '$vt' needs Sony's $vt_tbl, which this player's firmware does not include and no install has supplied — stock curve stays (put it at the top of the drive and install again)"
711758
elif "$VOLTABLE_BIN" "$vt" >/dev/null 2>&1; then
712759
vt_log "volume curve: $vt applied"
713760
else

0 commit comments

Comments
 (0)