How Two Low-Severity Bugs Rooted a Car's Navigation Unit

A car’s navigation unit had signed updates and noexec mounts, and both held. It still fell to two bugs that each looked like a dead end: a log command that could write to the wrong path, and a program that crashes on start.

In Jailbreaking my car’s navigation system just so I can change the time, Rasmus Moorats walks through rooting the head unit of his 2014 Renault Latitude. Below are the two obvious attacks, why each failed, the chain that worked, and what to take into your own bug triage.

Table of Contents

Why he needed root at all

Estonia switched to winter time, and the dashboard clock was an hour off. The setting wasn’t there. Moorats found that the unit, a TomTom Carminat LIVE on Linux 2.6.28.10, reads the menu entry for the Settings applet from the map SD card itself.

That card has to be a genuine one. Moorats describes DRM checks tied to the card’s CID, so copied files on a blank card get rejected. A replacement card would have cost him roughly 60 EUR with shipping. He chose to root it.

Plan A: flash a modified update

The update files contained the root filesystem as an ext3 image. The obvious move was to mount it, edit the init scripts, and let the unit flash it from an SD card.

It refused. The unit happily offered to install unmodified images, but never his. Using a QEMU emulator he built for the unit, Moorats found high-entropy data appended to the image, ending in 40 bytes he took for a signature, possibly DSA with SHA-1. Without the verifying binary, there was nothing to attack.

Plan B: use the built-in shell command

The unit runs a master.script from the SD card on boot, in a custom scripting language. Its parser has a SHEX command that launches a command line through execve.

Two limits killed it. SHEX accepts only one whitespace-delimited token, so no arguments. And per Moorats, most mounts were read-only, noexec or both, including the SD card. He could run binaries already on the rootfs and nothing else. He called the setup “surprisingly strong security for something from 2011.”

Plan C: chain two weak bugs

Moorats then listed what he had. He could run any rootfs binary without arguments. And the script’s LOG command had a classic path traversal: point LOGFILE at ../ paths and it writes one line of printable ASCII anywhere, as root.

Neither helped alone. The fix came from a kernel-exploit trick: write to /proc/sys/kernel/core_pattern. A pattern starting with | tells the kernel to pipe core dumps to a program, with arguments. The final script was three lines:

#define LOGFILE ../../proc/sys/kernel/core_pattern
LOG |/bin/sh /media/sdcard/payload.sh
SHEX /bin/dbus-monitor

dbus-monitor aborts when there’s no system bus, which this unit doesn’t run. The crash made the kernel launch /bin/sh from the rootfs with his script, as root. His payload backed up the eMMC with dd. The clock lived in /mnt/flash/settings.dat as an offset in minutes. He changed 180 to 120.

Was it worth it?

One developer in the discussion said he should have just bought the card and trusted the previous owner. Moorats replied that the old card had already gone in the trash.

Others pointed at the design itself. One described a car whose clock stopped syncing once its cellular service went away, and another a car that updates the time zone only if you share driving data. On the security side, the hardening stopped every direct attack. The unit still fell because one parser trusted a path.

What to change in your bug triage

The lesson for teams shipping Linux on devices, or anything else, is to score a bug by what it can combine with.

  • Treat any write-anywhere bug as high severity, even if it only writes printable text. /proc and /sys accept text.
  • Lock down kernel.core_pattern on devices and containers, or set it at boot and drop write access to /proc/sys for unprivileged services.
  • Don’t run script parsers as root. ttn ran as root, so its log writes did too.
  • Rank “it just crashes” bugs by what else is on the box. Here a crash was the trigger.
  • When you review a report, ask what the attacker already has. Moorats had two primitives no one would rate high alone.

FAQ

What is core_pattern?

It’s a kernel setting at /proc/sys/kernel/core_pattern that controls where core dumps go. If it starts with |, the kernel runs that program on every crash and pipes the dump to it. Support for arguments landed in Linux 2.6.24, per Moorats.

Why didn’t noexec stop the payload?

Noexec blocks running files from that mount directly. The kernel ran /bin/sh from the rootfs, which wasn’t noexec, and passed the script as an argument.

Is path traversal in a log file really serious?

On this unit it was the key half of a root exploit. Anything that lets an attacker choose the file path is a write primitive, and its severity depends on what files exist.

Do signed updates still matter?

Yes. The signature blocked the easiest attack outright. It just doesn’t cover code paths that run after boot, like a script parser.

Did he need special hardware?

No. He used the firmware from public update URLs, a QEMU emulator he built, and an SD card.

How did he find a binary that crashes?

He ran almost every binary on the system in his emulator. dbus-monitor raised SIGABRT when it couldn’t reach the system bus.

Sources