Recently I discovered, almost by accident, that:
CoreAudio in macOS contains a parser for the iMelody format. This is a non-polyphonic ringtone format that Sony Ericsson developed for its mobile phones back in the first half of the 2000s. Don’t ask me why a parser for this format ever ended up in a macOS system library, and — more importantly — why it’s still there. I have no idea.
This parser has a bug that leads to an OOB read. The bug has probably gone unnoticed for decades. Right now it’s completely useless from an exploitation standpoint, for several reasons I’ll get into below.
So get comfortable — this is going to be a short and fairly fun blog post (assuming your idea of fun is as peculiar as mine).
General information about the iMelody format
The format is quite simple:
The file extension is usually
.imy.The file format is textual — specifically, a sequence of
KEY:valuefields (yes, exactly like that, with no spaces around the:).The key is always an uppercase string.
The value can be a string, an integer, or a float, so the structure of an
.imyfile is flat — the format doesn’t support anything like a dictionary or an array/list.Each field occupies a single line.
The order of fields matters: every file must begin with
BEGIN:IMELODYand end withEND:IMELODY, and between them you’ll typically find the following fields:VERSION:1.2FORMAT:CLASS1.0orFORMAT:CLASS2.0NAME:<the name of the ringtone>COMPOSER:<the composer's name>BEAT:<25 ... 900>STYLE:<S0 ... S2>VOLUME:<V0 ... V15>to set the volume,VOLUME:V+to increase the current volume, orVOLUME:V-to decrease itCOPYRIGHT:FREEorCOPYRIGHT:PROTECTEDMELODY:<the ringtone>— this is a string, a sequence of characters with no spaces that encodes the ringtone itself. The format of this string is a separate story, which we’ll come back to in the next section.
The mandatory fields are
VERSION,FORMAT, andMELODY. The remaining key-value pairs are optional, but in real.imyfiles (the few you can still find these days) they’re usually present.
An example .imy file:
BEGIN:IMELODY
VERSION:1.2
FORMAT:CLASS1.0
NAME:Test
BEAT:120
STYLE:S1
VOLUME:V15
MELODY:c1d2e3f4g5
END:IMELODY
This is, of course, far from a complete description of the iMelody format. You can read more about it on Wikipedia or in the specification “Sony Ericsson W800i White Paper”, which I see referenced everywhere but cannot find anywhere (no surprise — more than 20 years have passed). I also highly recommend taking a look at the documentation for the fan project https://imyplay.sourceforge.io/; that’s where I learned most of the details about iMelody.
Overall, the format is catastrophically outdated, so there’s very little information about it.
The MELODY format
This rather long and detailed section is entirely dedicated to the format of the MELODY field. And this format is not exactly all that fun. I promised a short and fun blog post, so you can skip this section and jump straight to the bug. It’s ok, just remember that if there is an opening parenthesis '(' in the MELODY field, there must also be a closing one ')'.
But if you are still interested in reading about the format of the MELODY field, let me start with a warning.
WARNING! I wrote this section together with Claude, since I don’t understand anything about musical notation myself. If someone with a musical education reads this and finds mistakes — apologies, Claude and I did what we could.
So… the MELODY field is a sequence of one or more elements joined together with no separators between them. Each element is one of: a note, a silence (rest), a volume change, an LED control, a vibrate control, a backlight control, or a repeat block. The player processes them left to right.
Notes
A note is written as several parts in this order:
- Optional octave prefix —
*0through*8, setting which octave the note sounds in.*4means A = 880 Hz and is the default if you omit the prefix. The chosen octave stays in effect until you specify a different one. - The pitch (mandatory) — one of:
- Basic notes:
c d e f g a b - Flat notes:
&d &e &g &a &b(the&prefixes the letter) - Sharp notes:
#c #d #f #g #a(the#prefixes the letter)
- Basic notes:
- Duration digit (mandatory) —
0=whole,1=½,2=¼,3=⅛,4=1/16,5=1/32. - Optional duration specifier — modifies the duration:
.=dotted,:=double-dotted,;=two-thirds length. Omitting it means no modification.
Example: *4g3 is a G in octave 4 as an eighth note; #d1 is a D-sharp half note.
Silences (rests)
A rest is the letter r followed by a duration digit and an optional duration specifier, exactly like a note’s timing part. Example: r3 is an eighth-note rest.
Volume
Volume elements can appear anywhere in the melody:
- Absolute levels:
V0(tone off) throughV15;V7is the default. - Relative modifiers:
V+raises andV-lowers the volume by one step from the current level.
A volume setting persists until changed.
Non-audio controls
These toggle device features inline and can appear between notes:
- LED:
ledon/ledoff - Vibrate:
vibeon/vibeoff - Backlight:
backon/backoff
Repeat blocks
A repeat groups a run of elements to be played more than once. The syntax is:
( <elements> @ <repeat-count> [<volume-modifier>] )
- Open with
(, then one or more elements (notes, silences, LED/vibe/backlight controls, volume changes — but not another repeat block). - Then
@followed by a repeat count: how many times to play the group. A count of0means repeat forever. - Optionally, a volume modifier (
V+/V-) right before the closing).
Two things to note about volume inside a repeat:
- A
V+/V-placed anywhere inside the block adjusts volume as the block plays, and that change carries over after the loop ends (it’s not reset). - Repeat blocks cannot be nested in CLASS1.0 — you can’t put a
(...)inside another(...).
Example: (#d1r3d2e2@3V+) plays the group #d1 r3 d2 e2 three times, stepping the volume up.
Style interaction (context)
The MELODY content itself doesn’t encode note-vs-rest spacing; that comes from the separate STYLE field (S0 natural with a rest between notes, S1 continuous, S2 staccato), and tempo comes from BEAT. The melody string only specifies pitches, durations, volume, and device toggles.
Example
Let’s look at an example:
V7&b2#c3V-c2*4g3d3V+#d1r3d2e2:d1V+f2f3.
Reading it out: set volume 7 → B-flat ¼ note → C-sharp ⅛ note → volume down → C ¼ note → switch to octave 4, G ⅛ → D ⅛ → volume up → D-sharp ½ → ⅛ rest → D ¼ → E double-dotted ¼ → D ½ → volume up → F ¼ → F dotted ⅛.
The bug
This section describes the actual bug and why it is not a vulnerability.
The OOB read in the loop
To see the actual bug, let’s make sure that on your Mac:
- macOS 26.5.2 (the latest version at the time of writing this blog post) is installed
dyld-shared-cache-extractoris installed
If dyld-shared-cache-extractor isn’t installed, let’s install it:
brew install keith/formulae/dyld-shared-cache-extractor
First of all, extract dyld_shared_cache_arm64e:
dyld-shared-cache-extractor \
/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e \
./macos26.5.2-libs/
Among the extracted files, find
./macos26.5.2-libs/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox
and open this file in any disassembler. I use Binary Ninja, but Ghidra or Hopper will also work. Then, using symbol navigation, find the Sequence::ReadiMelodyFile method and the loop inside it:
int64_t Sequence::ReadiMelodyFile(TStream& arg1, uint32_t arg2) + 1156:
@loop:
; look for the closing parenthesis ')'
mov x0, x19 ; start of the loop: x0 = this (the code uses a special reader object to read bytes)
bl sub_1943e12e0 ; call the reader: read the next byte into x0
cmp w0, #0x29 ; is it ')'?
b.ne @loop ; no! back to the start of the loop — read another byte!
...
This loop starts executing after an opening parenthesis '(' is encountered in the MELODY string. Its purpose is to find the matching closing parenthesis ')'. Suppose our iMelody file looks like this:
BEGIN:IMELODY
VERSION:1.2
FORMAT:CLASS1.0
MELODY:(c1@5V+
END:IMELODY
(there’s no closing parenthesis in MELODY). In this case the loop will read byte after byte, and will only stop once a ')' shows up somewhere in memory, or the allocated memory simply runs out. A textbook OOB read.
Why this is a bug, not a vulnerability
There are two main reasons:
First and foremost, the
Sequence::ReadiMelodyFilemethod is “dead” code. Yes, this method is present inAudioToolboxand really does contain the aforementioned loop with the OOB read. But there appears to be no realistic scenario in modern macOS or iOS for calling this method at all. There’s no attack surface. That’s also why there’s no PoC in this blog post — because writing one is, most likely, fundamentally impossible.Second, this OOB read returns nothing — that is, the only benefit to an attacker is a potential DoS. It’s hard to even call it a primitive, in the sense that term is used when describing binary vulnerabilities.
Zero security impact. That’s exactly why I didn’t report this OOB read to Apple.
Speaking of Apple…
…if they ever read this post, I have one recommendation.
Don’t fix anything!
Just remove the iMelody parser from CoreAudio. It’s 2026, it’s about time.
ALTV!ST