Part 2: Risks and Vulnerabilities in Hardware Systems
What a Samsung Galaxy S25 Ultra Trusts Without Asking
Nine confirmed kernel-driver bugs on a current flagship phone — from a nameplug-in USB-C cable to component-level tampering, across six independent chip vendors.
A follow-up to “Risks and Vulnerabilities in Hardware Systems: A Case for Greater Awareness”.
The premise, again
In the first post in this series, I looked at a handful of Linux kernel drivers — an AMD display driver, a Toshiba HDMI-to-MIPI bridge, an NFC chip driver, an emulated keyboard — and found the same shape of bug in each one: the driver trusted a length, a count, or an index reported by a piece of hardware, and used it to write into memory without ever checking whether it made sense. The chip says “here are 40 bytes,” the driver allocates room for 8, and writes 40 anyway.
Upstream Linux has a name for why this keeps happening: hardware on an internal bus is, by policy, trusted. Your touchscreen, your battery gauge, your Bluetooth chip — none of them are treated as adversarial input sources. That’s a reasonable engineering trade-off in a world where the parts on your motherboard came from the factory and nowhere else. It’s a much shakier one in a world of third-party repair, aftermarket batteries, and phones that get resold, repaired, and re-repaired for years — where a “trusted” chip can, in practice, be a chip nobody vetted.
I raised this directly with people close to the upstream kernel security process, and the answer I got back was blunt: hardware-to-driver trust isn’t meaningfully part of the Linux kernel’s threat model at all. The security boundary upstream actually defends starts at the syscall interface — userspace is assumed hostile, the chip behind a driver is not. That’s not a flaw in any one driver; it’s a gap in what the threat model even considers, and it’s a big part of why the same shape of mistake keeps turning up across completely unrelated chip vendors — nobody upstream is chartered to go looking for it.
That thesis held up in the first post’s examples, which were older devices and smaller drivers. The question I wanted to answer this time was: does it hold up on a current flagship, running today’s kernel, from a vendor that ships a serious security stack? So I picked the Samsung Galaxy S25 Ultra — Knox, RKP, DEFEX, the works — pulled the actual GPL kernel source Samsung publishes for it, and went looking.
The short answer: yes, and the pattern shows up in six completely unrelated chip vendors on the same phone. Below are the findings, organized by something more useful than “how bad is it in the abstract” — organized by how someone actually gets there.
Four ways in, not one severity scale
Most vulnerability writeups rank findings by exploit strength: this one gets code execution, that one just crashes. That’s a real axis and I’ll use it below — but it buries the more interesting question for a phone in your pocket: what does an attacker actually have to do? “Solder a new chip onto the board” and “plug in a USB cable” are wildly different threats even when the underlying bug class is identical. So I’ve grouped these findings by access model instead:
- Plug-and-play — no disassembly at all. Something you insert into the port.
- Consumer/repair-shop replaceable parts — components normal people (and every phone repair shop on the planet) swap without any tools most people don’t own.
- Component-level tampering — requires opening the phone.
- None of the above — no hardware involved at all, but only reachable by a process that’s already privileged. One finding, discussed near the end, belongs here on its own.
Two things worth stating up front about the first three of these — the physical-access doors. The fourth (no hardware at all) doesn’t inherit either point, for reasons worth being explicit about rather than just skipping past:
Supply chain, not just “someone hands you a bad part.” Every one of the first three access models has a supply-chain version, not just a point-of-use version. A malicious USB-C cable doesn’t have to come from a stranger at a coffee shop — it can just as easily be a counterfeit “compatible” cable that was already carrying a malicious controller when it left the factory. A tampered battery doesn’t require someone to open your phone — it can arrive that way from an aftermarket parts supplier. And component-level tampering doesn’t require an attacker to physically touch your specific device at all if it happens earlier, at a repair vendor or in transit. “Physical access required” doesn’t mean “targeted attack only” — it can mean “anywhere in the chain between the factory and your hands.” None of that applies to the fourth door — there’s no physical part involved anywhere in its chain, so there’s no supply chain to poison.
Getting real kernel-level compromise this way is also just what “rooting” a phone against its owner’s knowledge looks like — and that’s true regardless of which of the first three doors it comes through. Samsung Knox, verified boot, and the bootloader lock all assume the kernel itself is trustworthy. Any bug that actually achieves memory corruption (not just a crash — I’ll be explicit below about which of these do and don’t clear that bar) is a way around that entire security model rather than through it, whether it arrived via a cable, a battery, or a swapped chip. I’ll call this out again where it matters most, but it’s not exclusive to any one of those three. The fourth finding doesn’t clear this bar either, for a different reason: it’s a read, not a memory-corruption primitive, so it isn’t the same kind of “quiet root” on its own.
Plug-and-play: the USB-C cable
Every one of you has plugged an unfamiliar USB-C cable, dock, or charger into your phone at some point — a friend’s cable, a hotel-room dock, a public charging station. “Don’t plug into random USB ports” is folklore most people have half-internalized without knowing exactly why. This finding gives that folklore a specific mechanism, on a specific chip, on a phone you can buy today.
What this chip does and where it sits. The Maxim MAX77775 is the S25’s USB-C power-delivery controller — the chip wired directly behind the physical port that negotiates every plug-in event: how much power to draw, what charging mode to use, and whether whatever you just plugged in identifies itself as a Samsung accessory (like a DeX dock). It’s the first chip on the phone that talks to anything inserted into the port, before Android, before any app, before you’ve even unlocked the screen.
The bug
It also supports a Samsung-proprietary extension to the standard USB-PD protocol, used for things like DeX dock detection. When a “Samsung-flagged” accessory attaches, the phone and accessory negotiate, and the accessory can send back multi-part data packets — think of it as the accessory saying “here’s part 1 of 4, here’s part 2 of 4,” and so on.
The driver reassembles these parts into a fixed 255-byte buffer. Here’s the shape of the reassembly loop, simplified from the actual driver code:
uint8_t IN_DATA[255]; // fixed-size buffer, lives on the stack
int cur_uvdmset_num = 0;
while (cur_uvdmset_num < total_uvdmset_num) {
// read the next 16-byte chunk the accessory just sent
receive_next_chunk(&IN_DATA[cur_uvdmset_num * 16]);
// the accessory tells us which chunk number this was —
// nothing requires it to actually increment
cur_uvdmset_num = accessory_reported_chunk_number();
}The problem: nothing checks that cur_uvdmset_num * 16 stays inside the 255-byte buffer, and - this is the part that upgrades it from "a fixed small overflow" to something worse - the accessory controls cur_uvdmset_num directly, and nothing requires it to actually count up. A malicious accessory can just keep reporting "this is chunk 0" forever, and the phone will happily keep appending 16 more bytes past the end of the buffer with every single message, for as long as the accessory wants to keep talking. There's no cap. A total_uvdmset_num of 1 set in the very first packet is enough to arm the loop, and after that the accessory can simply never advance the counter and let the overflow grow unbounded, limited only by how long it's willing to keep sending messages (about one every two seconds).
Reaching this at all requires nothing more than claiming Samsung’s vendor ID during the normal, automatic handshake every accessory goes through when it’s plugged in — no user interaction, no pairing, no confirmation dialog.
But here’s the honest part
The buffer above lives on the stack, not the heap. Modern kernels — this one included — place a random guard value (a “stack canary”) right after stack-allocated arrays like this one, specifically so that a sequential overflow like this one has to walk across the canary before it can reach anything more useful, like the address the function will jump back to when it’s done. When it does, the kernel doesn’t try to recover gracefully — it panics on purpose.
That’s almost certainly what happens here. This bug is real, it’s triggerable by a five-dollar cable with the right chip inside, and Samsung should fix it — but the realistic outcome, as shipped, is your phone crashes and reboots, not that an attacker gets a foothold. I’d rather tell you that plainly than let a crash sound scarier than it is. (More on why the canary matters so much for grading these bugs in a moment)
Who can even trigger it, if the accessory succeeds? I went and checked this against Samsung’s actual shipping security policy (not just the kernel source — the real policy file baked into a retail S25 Ultra), and the answer is interesting: the code path that ultimately issues the command is only reachable by system_server - core Android platform infrastructure, not any third-party app, but also not something buried behind root. That's consistent with this firing automatically the moment a DeX-flagged accessory attaches, which is exactly the "zero interaction needed" story that makes plugging in a cable scary in the first place.
The part you (or your repair shop) can swap yourself
This group turned out bigger than I first framed it. It’s not just the battery — the touchscreen and S-Pen digitizer belong here too, and the reason is simple: none of these three require opening the phone to a specialist level or building custom hardware. They’re all reachable through a routine parts swap, the kind any repair shop (or a supply chain feeding one) performs every day.
Your touchscreen and S-Pen: the same display assembly, the same aftermarket supply chain
What these chips do and where they sit. The touch controller (a Synaptics S3916T) and the S-Pen digitizer (a Wacom chip) both live in the display module — the touch controller shares a single flex cable with the display panel itself, and the digitizer is the sensor layer beneath the screen that tracks the S-Pen’s position and pressure. Both get replaced together as part of the same component: the display assembly.
That matters, because display assembly replacement is one of the most common phone repairs there is, and the aftermarket supply chain for it is large, well established, and openly sells parts that differ from the original — including the touch controller itself. Third-party replacement displays for Samsung phones routinely ship with third-party touch ICs rather than Samsung’s originals; it’s a documented, known quantity in the repair industry, not a hypothetical. A tampered aftermarket display carrying a modified touch controller is, procedurally, the same difficulty as the tampered battery below — no custom interposer, no bus-splicing, just a part that gets installed the normal way.
The strongest bug in this whole set lives in the touch driver. Every touch report the chip sends up includes an object index — which finger, which contact point — and the driver uses that index directly to write into a fixed array of 10 slots:
struct touch_object object_data[10]; // MAX_NUM_OBJECTS = 10
int obj = read_object_index_from_report(); // straight from the chip, no check
object_data[obj].x_pos = read_x_from_report();
object_data[obj].y_pos = read_y_from_report();
// ...six more fields, same patternThere is no check anywhere in this path that obj is less than 10. None. And unlike the USB-C and battery bugs, this array isn't on the stack - it's a driver-owned structure that lives for as long as the touch driver is running. There's no canary anywhere near it. If a substituted touch controller reports an out-of-range index, whatever the kernel put in memory right after that array just gets silently overwritten, with no crash and no warning.
Get Mikey Strauss’s stories in your inbox
Join Medium for free to get updates from this writer.
Here’s the part I find genuinely interesting: the vulnerable code isn’t some obscure corner — it’s a protocol Samsung tried to retire and never actually managed to shut off. This chip speaks two different report formats. Right after power-on, it defaults to its own native, low-level format — call it the “factory” format — and the driver’s job during boot is to tell the chip “stop using that, switch to the simpler format we actually want.” From that point on, ordinary touches use the simpler format, which goes through completely different, safe code and never comes near the bug above.
But “tell the chip to stop sending the old format” only stops the chip from sending it voluntarily. It does nothing to stop the driver from processing one if it shows up anyway — and the code checks what kind of message it received only after it’s already been parsed. So the vulnerable path isn’t a leftover debug mode nobody uses; it’s the chip’s own original, native protocol, still fully wired up, that a malicious or substituted controller can invoke at any time after boot, not just in the first second the phone is on.
Where the physical side of this stands right now: the realistic path is the tampered-aftermarket-display scenario above, and I haven’t built or sourced one to confirm the exact frame format a substituted controller would send. Separately, I also mapped the real SPI wiring off the phone’s own public FCC compliance filing and designed a small transparent interposer that would sit on the shared flex cable and swap in a crafted frame — a harder, more surgical path aimed at tampering with a genuine, already-in-hand phone rather than the supply chain. I have not built or tested that physical board yet — what exists today is a complete, hardware-verified design, not a working demo. Constructing a valid frame in the chip’s native format requires first capturing the exact bit layout it reports at boot, which means monitoring the SPI bus during a real boot sequence — that capture is the next concrete step for that path, not yet done.
The S-Pen digitizer carries the same access story — same display assembly, same aftermarket parts market — but a different bug. A factory diagnostic command sends a request to it and reads back a 38-byte reply. One byte in that reply is used, completely unchecked, to compute both a write offset and drives writes of a value also taken straight from the same reply — into a heap buffer about 2.7KB in size. The unchecked byte can push the write index roughly seven times past the end of that buffer, with an attacker fully controlling both where the write lands and what it writes. That’s a stronger, more flexible primitive than the touch bug — the tradeoff is it needs a specific diagnostic command triggered first, which I traced against the real phone’s security policy and found is reachable by any Samsung-signed system app, not literally anyone, but a meaningfully larger surface than “root only.”
The chip whose entire job is trust
What this chip does and where it sits. Your phone’s battery isn’t just a battery — embedded inside the pack itself is a small companion chip (a Maxim DS28E30), wired to the mainboard over a single-wire (1-Wire) bus that every replacement battery, genuine or counterfeit, has to touch just to power the phone at all. Its sole purpose is proving the battery is genuine, using a real cryptographic challenge-response protocol, so the phone can refuse to fast-charge (or refuse entirely) a counterfeit pack. It’s a small irony, then, that this chip can itself corrupt the kernel.
The battery’s little authentication chip reports its own response length as part of its protocol — “here’s how many bytes I’m about to send you.” The driver trusts that number and loops that many times, writing each byte into a 255-byte buffer:
uint8_t read_buf[255];
uint8_t response_length = read_byte(); // the *chip* reports this
for (int i = 0; i < response_length + 2; i++) {
read_buf[i] = read_byte();
}If the chip reports a length of exactly 0xFE (254), the loop writes 256 bytes into a 255-byte buffer - one byte past the end. It's a narrow overflow (this one really is capped, unlike the USB-C bug above), but it's reachable every single time the phone boots, from the moment any battery - genuine, aftermarket, or a bare 1-Wire chip on a bench - answers on the bus, with no authentication required to reach it. A malicious replacement battery is one of the physically easiest swaps on the entire device.
Same canary caveat applies here — this is also a stack buffer, also a sequential loop — so the realistic outcome is likely a crash rather than corruption. But the story stands on its own regardless of the technical ceiling: the anti-counterfeit chip is not exempt from the same input-validation discipline as everything else, and “swap the battery” is a threat model repair shops and their customers should be able to reason about.
Three vendors — Synaptics, Wacom, Maxim — three completely independent driver subtrees, and the same missing check in the same shape, all reachable through parts any repair shop swaps routinely.
Open the phone — and why that’s not just an academic threat model
This last finding sits in a different category. Unlike the display assembly and the battery, I didn’t find a comparable aftermarket-parts market for this component — camera modules are more specialized, and I don’t have evidence of a documented supply chain shipping tampered ones the way screens and batteries have. So this one genuinely requires opening the phone to a deeper level: a compromised repair channel or a targeted device implant, where someone already has extended physical access and time, rather than a part that swaps in the normal course of a repair.
What this chip does and where it sits. The laser autofocus sensor (an ST VL53L8 ToF sensor) sits next to the rear camera cluster and bounces laser pulses off whatever the camera is pointed at to help it focus faster, reporting the results back over I2C on every shot.
This one’s actually two separate bugs, both from the same root cause: the sensor reports how many measurement “zones” it captured, and a filtering step in the driver trusts that number completely, walking a fixed 68-entry array with an index that can run into the tens of thousands. One of the two bugs both reads and writes out of bounds and fires on essentially every camera-autofocus operation; the second is read-only but has no gate on it whatsoever — not even the loose check the first one has. Both land in heap memory the driver allocated once at startup, same as the touch and pen bugs above — no canary anywhere near either of them.
(I also found, and want to be upfront about, a fourth candidate that looked like it might collapse this entire category down to something requiring no physical access at all — a debug interface on the ToF sensor that seemed to let ordinary software inject data into the same buffer real sensor readings land in. I spent real effort trying to make that theory work, tracing every code path by hand, and it doesn’t: a single lock in the driver makes it structurally impossible for the debug path and the real sensor-data path to interfere with each other, not just unlikely. I’d rather tell you a hypothesis died than let it stand unchallenged. It wasn’t a total dead end, though — chasing it down turned up a smaller, genuinely hardware-free bug of its own: the same debug interface can be tricked into making the kernel write through a null pointer, which is a real crash bug, just a much less interesting one than what I was originally chasing.)
The one that needs no hardware at all
Every finding above needs some physical-world action — a cable, a swapped part, an opened phone. This last one doesn’t. It also doesn’t fit the “attacker” framing quite the same way, so I want to be extra precise about what it actually requires.
What this chip does and where it sits. The CS40L26 (a Cirrus Logic part) is the driver behind your phone’s haptic motor — the thing that buzzes when you tap the keyboard or get a notification. Like the other findings, it accepts custom effect data over a standard path: the same Linux mechanism (EVIOCSFF, a "force-feedback upload" call) that's been part of the kernel's input subsystem for decades, originally built for things like game controller rumble.
The bug. Part of uploading a custom effect involves telling the driver how many data “sections” your effect contains, then handing it the actual section data. The driver reads that count and loops through the buffer that many times — but never checks that the buffer you actually sent is long enough to contain that many sections. A short buffer paired with a count that claims more sections than it has causes the driver to read past the end of a small heap allocation. This is a read, not a write — it leaks whatever memory happens to sit past the buffer, not corrupt it, though the destination for that leaked data is unusual: it gets written into the haptic chip’s own onboard memory over the control bus, not handed back to an app directly. I traced that far and no further — whether anything can subsequently read that leaked data back out of the chip is a real open question I don’t have an answer to.
Who can actually trigger it — and this is the part that matters most. In principle, EVIOCSFF is something any process could call if it can open the right device file. In practice, I checked the real security policy on this phone, and it's locked down tighter here than almost anywhere else in this whole set: no ordinary app can open that device file, and neither can system_server — the broad, everything-touching Android system process that turned out to be able to reach two of the other bugs in this post. The only things that can are a handful of specific, already-privileged background processes, most plausibly the one that legitimately implements Android's vibration API. And even for those, I checked what that API actually exposes to apps: structured commands like "play primitive effect #4 at 80% strength," not "here's a raw block of bytes, upload it verbatim." So the realistic path to actually triggering this isn't "a malicious app calls the vibration API" — it's "something has already gone wrong with the one process that's allowed to talk to the haptic chip directly." That's a genuinely different, narrower bar than every other finding in this post, and I want to be upfront that it's the weakest one here on reachability grounds, even though the underlying bug itself is real and, unlike the USB-C and battery bugs, lands in memory a stack canary can't protect.
The same bug exists on a Google Pixel too — unfixed, independently. Since this driver isn’t Samsung’s own code, I checked whether it shows up elsewhere. It does: the identical driver, with the identical missing check, is present and unpatched in Google’s own published Pixel kernel source as of this writing. Google’s security policy is, if anything, even stricter here than Samsung’s — there’s an explicit rule blocking every single app from touching this device file at all, not just an absence of permission. Same story either way: real bug, requires an already-privileged process to reach, present on more than one vendor’s phones.
(Everything above came from reading source code, reading the phone’s own security policy, and reading strings out of the actual vibration-service binary already installed on the device — no ioctl was issued, no effect was uploaded, and nothing was triggered against a live phone to produce any of it.)
Wait — does the kernel already protect against this stuff?
Short answer: sort of, and which findings it protects tells you almost everything about which ones actually matter.
Modern kernels ship with the stack-canary protection I mentioned above for the USB-C and battery findings. It’s a real, useful defense — but it only covers one specific kind of memory: local variables that live on the stack, inside a single function call, in a buffer that gets overflowed by something walking forward through it byte by byte. It does nothing for two other kinds of memory a driver commonly writes into: the heap (memory allocated once and kept around for as long as the chip is connected), and another process’s memory entirely.
That split lines up almost exactly with the groups above. The USB-C cable bug and the battery-authenticator bug both overflow small stack buffers, in loops that walk forward one chunk at a time — textbook cases for the canary to catch. The touchscreen, S-Pen, and ToF sensor bugs all write into heap memory the driver allocated once at boot and never revisits — no canary in the world helps there. The haptics bug is heap too, though it’s a read rather than a write. (There’s a heap-equivalent protection, something like KASAN, but it’s a development tool with real runtime cost; it’s essentially never shipped on a retail phone, and it isn’t here either.)
One more finding — a bug in the UWB (ultra-wideband) radio driver — is its own category entirely: it doesn’t overflow the kernel’s memory at all. It ignores the standard rules of a read() system call and hands back more data than the calling app asked for, overflowing that app's buffer instead. The kernel's stack canary has no visibility into what happens in a different process's memory once the kernel hands bytes over - whatever protections exist there belong to that app, not the kernel, and I don't have visibility into that from driver source alone.
None of this changes whether these are real bugs — every one of them is, and every one of them was checked twice, by two independent passes, specifically trying to find the reason it wasn’t real before I believed it. What it changes is how seriously to take what happens if it fires. That’s a more honest map of the actual risk than treating “found an out-of-bounds write” as one flat severity, and it’s exactly the kind of distinction I think this whole research area needs more of.
We went and checked who can actually reach these
Source code tells you a bug exists. It doesn’t tell you who can trigger it on a real phone — that lives in a separate security layer (SELinux policy) baked into the actual retail firmware, not the public kernel source. So I went and got the real, shipping firmware for this exact phone and checked every one of these findings against it directly.
That turned up two genuine surprises, both corrections in the right direction (more reachable than assumed, not less):
- The S-Pen bug’s trigger command isn’t locked down to root, as the initial read of the source suggested — it’s reachable by any Samsung-signed system app.
- The USB-C bug’s trigger is directly reachable by
system_server, core Android platform code — supporting, though not proving outright, that it fires automatically the moment a flagged accessory attaches, no app or user step required.
It also settled a naming question the source alone can’t answer: the ToF sensor’s device file isn’t the /dev/vl53l8 you'd guess from the driver name — it's /dev/range_sensor, confirmed against the actual compiled driver. That's the kind of detail that only surfaces by checking the real, shipping hardware instead of stopping at the source.
The same check gave the haptics bug its narrowest result of the whole set — see above — and while I was at it, I also compared it against a second phone entirely, a Google Pixel, using the same “read the real installed policy, don’t assume” method. Worth doing once you have the tooling built anyway.
What this isn’t
To be clear about what I’m not claiming: this is not “Samsung’s kernel is riddled with holes.” I screened a couple dozen chip-facing drivers on this device — the fingerprint sensor, the NFC controller, the audio codec stack, several charging-related chips, the display panel path — and the overwhelming majority of them validate device-supplied data correctly. The fingerprint sensor in particular is worth calling out as a genuinely good design: all the sensitive biometric processing happens inside a hardware-isolated trust zone the regular kernel never even touches. That’s not an accident, and it’s worth crediting.
The point isn’t that this phone is uniquely bad. It’s that the same class of gap I found on much older, smaller devices in the first post in this series is alive and well on a brand-new flagship with a serious dedicated security team — in three unrelated chip vendors, on the exact same device. “The hardware is trusted” is doing more work in modern kernel driver code than it should be, and it’s worth every vendor’s while to go looking for where.
Full technical detail, exact code locations, and the complete list of what was checked and cleared is being prepared as a formal writeup for vendor disclosure and conference submission. This post intentionally omits exact file paths, line numbers, and the full injection-protocol byte layout — coordinated disclosure comes first.









