10 interesting stories served every morning and every evening.

How we saved 100 terabytes of memory by optimizing 1.1.1.1’s DNS cache

blog.cloudflare.com

Big Pineapple, the plat­form be­hind 1.1.1.1, Gateway DNS, DNS Firewall, AS112, and sev­eral other Cloudflare DNS ser­vices, stores over 250 bil­lion DNS cache en­tries at any given time. At that scale, wast­ing a sin­gle byte per en­try costs more than 250 gi­ga­bytes of mem­ory across our fleet.

Five suc­ces­sive changes to how cache en­tries are stored in mem­ory cut the per-en­try foot­print by over 50%. Across our fleet, these changes freed up roughly 100 ter­abytes of mem­ory, equiv­a­lent to the amount of RAM in 130 of our Gen 13 servers. The cache also got faster. Insert through­put rose 43% and lookup la­tency dropped 19%, as fewer al­lo­ca­tions and bet­ter mem­ory lo­cal­ity meant we did not trade speed for space.

What we cache

On cold start, Big Pineapple starts out with an empty cache. As DNS queries ar­rive, the cache fills un­til it hits its max­i­mum en­try count, at which point we evict older or less pop­u­lar items to make room.

The ex­act cache size varies by data cen­ter. When EDNS Client Subnet (ECS) is in use, au­thor­i­ta­tive servers re­turn dif­fer­ent an­swers de­pend­ing on the clien­t’s net­work, so we cache mul­ti­ple ver­sions of the same query. This in­creases both the num­ber of en­tries and the mem­ory each one con­sumes, mak­ing the op­ti­miza­tions in this post es­pe­cially im­pact­ful for ECS-heavy lo­ca­tions.

Each item in the cache is a key-value pair. The key iden­ti­fies what was queried:

pub struct CacheKey { qname: Name, qtype: Rtype, au­then­ti­cated: bool, tag: Vec<u8>, }

The value stores the DNS re­sponse it­self: the an­swer, au­thor­ity, and ad­di­tional record sec­tions, along with meta­data like the cre­ation time, a hit counter, and the Time-to-Live (TTL).

pub struct CacheEntry { time­stamp: UnixTimeStamp, pub in­cep­tion: Instant, pub ttl: Ttl, pub hits: u32, pub an­swers: Vec<Record>, pub au­thor­ity: Vec<Record>, pub ad­di­tional: Vec<Record>, pub er­rors: Vec<ExtendedError>, … }

Both structs have room for im­prove­ment. Several fields use types that carry over­head we don’t need once the en­try is stored.

Benchmarking mem­ory us­age

To mea­sure the im­pact of each change, we bench­mark by fill­ing the cache with ran­domly gen­er­ated en­tries that roughly match the traf­fic dis­tri­b­u­tion we see in pro­duc­tion: 56% A records, 25% AAAA, and 19% TXT. Each en­try con­tains be­tween one and four records.

TXT records serve as a stand-in for all non-A/​AAAA record types in the bench­mark. Their size is ran­dom­ized be­tween 64 and 224 bytes, close to the av­er­age re­sponse size we see for vari­able-length record types.

We track mem­ory us­age us­ing a cus­tom al­lo­ca­tor that wraps Rust’s System al­lo­ca­tor and records the num­ber and size of al­lo­ca­tions per cache en­try. Alongside mem­ory, we mea­sure in­sert through­put and lookup la­tency across the full cache flow to make sure mem­ory sav­ings don’t come at the cost of per­for­mance.

These in­puts ap­prox­i­mate pro­duc­tion rather than re­pro­duce it ex­actly. Process mem­ory also de­pends on traf­fic mix, cache oc­cu­pancy, al­lo­ca­tor state, and mem­ory used out­side the cache. We there­fore mea­sured res­i­dent mem­ory across pro­duc­tion in­stances dur­ing the roll­out.

The cost of ca­pac­ity

Vec<T> stores three fields: a pointer to heap-al­lo­cated data, the cur­rent length, and the to­tal ca­pac­ity. When you push an item, Vec checks whether the length ex­ceeds the ca­pac­ity and re­al­lo­cates if needed. If there’s room, it just ap­pends the item and in­cre­ments the length.

Once we store a DNS re­sponse in the cache, how­ever, we never mod­ify it again. The ca­pac­ity field serves no pur­pose, but still costs 8 bytes per Vec. The over-al­lo­cated heap space is wasted as well, as a Vec with ca­pac­ity for eight items but only five stored leaves three slots un­used on the heap.

Using Box<[T]> solves both prob­lems. It can’t grow af­ter cre­ation, so it does­n’t need a ca­pac­ity field or re­serve space for fu­ture el­e­ments. The same ap­plies to String, which also car­ries a ca­pac­ity field. Box<str> drops it.

Each cache en­try stores 8 Vec and String fields. Replacing them with Box<[T]> and Box<str> saves 8 bytes per field, 64 bytes per en­try. It also elim­i­nates the ex­cess heap mem­ory that Vec re­serves for fu­ture growth. The com­bined sav­ings add up to over 15 ter­abytes with over 250 bil­lion cache en­tries.

Fewer lists, fewer point­ers

Rather than stor­ing the an­swer, au­thor­ity, and ad­di­tional sec­tions in sep­a­rate lists, we can store a sin­gle list with off­sets to the start of each sec­tion. Since DNS record counts per sec­tion fit in a u16, we can use a u16 (2 bytes) for each off­set, com­pared to the 8-byte pointer and 8-byte length that each sep­a­rate Box<[T]> re­quires.

This re­moves two lists, each with an 8-byte pointer and 8-byte length, and re­places them with two 2-byte off­sets, sav­ing 28 bytes per en­try.

These sav­ings do not al­ways map di­rectly to the num­ber of bytes re­moved from in­di­vid­ual fields. Rust in­serts padding to sat­isfy align­ment re­quire­ments and rounds a struc­t’s size up to a mul­ti­ple of its align­ment. Removing a small field can there­fore elim­i­nate ad­di­tional padding. For ex­am­ple, we also packed sev­eral boolean fields into a sin­gle bit­flag. This re­duced the sur­round­ing padding, caus­ing the struct to shrink by more than the size of the in­di­vid­ual booleans.

Dropping the owner

Each DNS record has an owner, the do­main the record be­longs to. In many cases, this owner is iden­ti­cal to the do­main be­ing queried. For ex­am­ple, a query for ex­am­ple.com A re­turns two records with the same owner:

$ dig ex­am­ple.com A

;; ANSWER SECTION: ex­am­ple.com. 300 IN A 198.51.100.1 ex­am­ple.com. 300 IN A 198.51.100.2

But when a CNAME is in­volved, for ex­am­ple, the record owner can dif­fer from the queried do­main:

$ dig ex­am­ple.com A

;; ANSWER SECTION: ex­am­ple.com. 300 IN CNAME cdn.ex­am­ple.com. cdn.ex­am­ple.com. 300 IN A 198.51.100.1 cdn.ex­am­ple.com. 300 IN A 198.51.100.2

The DNS wire for­mat han­dles re­peated own­ers us­ing name com­pres­sion, as de­fined in RFC 1035. Rather than en­cod­ing the same do­main twice, sub­se­quent oc­cur­rences store a 2-byte pointer to the first oc­cur­rence. A do­main like www.ex­am­ple.com can en­code just www fol­lowed by a pointer to where ex­am­ple.com al­ready ap­peared in the mes­sage.

This works well on the wire, but in our cache we store the full owner name along­side each record. Following com­pres­sion point­ers dur­ing cache lookups is ex­pen­sive on the hot path, so we trade mem­ory for speed.

Most records, how­ever, have an owner iden­ti­cal to the queried do­main. For those, we can drop the owner en­tirely and in­fer it at read time. When the owner dif­fers, such as the A records be­hind a CNAME, we store the full name.

pub struct Record { owner: Option<Box<Name>>, class: Class, ttl: Ttl, rtype: Rtype, data: RecordData, }

When owner is None, re­sponse con­struc­tion re­stores the queried do­main from the cache key, avoid­ing a heap al­lo­ca­tion. This means the record is no longer self-con­tained, but the cache key is al­ready avail­able dur­ing every lookup. When the owner dif­fers, Some stores a pointer to the full name on the heap.

In prac­tice, most cached records have an owner iden­ti­cal to the queried do­main, so the ma­jor­ity re­quire no heap al­lo­ca­tion for the owner field.

Enum siz­ing

Rust enums are sum types: each vari­ant can carry dif­fer­ent data, but the enum is al­ways the size of its largest vari­ant.

pub enum Option<T> { Some(T), None, }

Option is ei­ther Some and holds a value, or None and holds noth­ing. Both vari­ants take the same amount of mem­ory. The enum stores a tag in­di­cat­ing the ac­tive vari­ant, fol­lowed by space large enough for the largest vari­ant’s data. When the vari­ant is None, that space is un­used.

For record data, it seems nat­ural to store each DNS record type as an enum vari­ant:

pub enum RecordData { A(Ipv4Addr), Aaaa(Ipv6Addr), Txt(Txt), Naptr(Naptr), Svcb(Svcb), // … }

But the enum is al­ways as large as its largest vari­ant. In our case, that’s NAPTR at 136 bytes. It stores three vari­able-length text fields, a do­main name, and two in­te­gers. As a re­sult, the full enum, in­clud­ing the vari­ant tag and padding, be­comes 144 bytes.

An A record only needs 4 bytes, and an AAAA record needs 16 bytes. A and AAAA make up over 80% of our traf­fic, so most records waste over 120 bytes on padding. Since a sin­gle cache en­try can store many records this quickly adds up.

Boxing the vari­ants

To solve this prob­lem, we can box the larger vari­ants of the enum, mov­ing them to a sep­a­rate heap al­lo­ca­tion. The enum then stores an 8-byte pointer to the heap, where the data takes up only the size it ac­tu­ally re­quires.

pub enum RecordData { // Small and com­mon vari­ants are stored in­line A(Ipv4Addr), Aaaa(Ipv6Addr), // Large vari­ants are stored on the heap Txt(Box<Txt>), Naptr(Box<Naptr>), Svcb(Box<Svcb>), // … }

For A and AAAA records, this saves 120 bytes per record. Smaller vari­ant types like TXT and CNAME also ben­e­fit. They still oc­cupy the 24-byte enum, but their heap al­lo­ca­tion is sized to their ac­tual data rather than padded to 144 bytes. NAPTR, the largest vari­ant, ac­tu­ally pays slightly more. It now adds the cost of a heap pointer and al­lo­ca­tion over­head. But NAPTR records are rare in prac­tice, so the trade­off is worth it.

But box­ing the larger record vari­ants in­tro­duces costs of its own.

The costs of box­ing

Boxing has two costs. The first is al­lo­ca­tor over­head. Each boxed vari­ant be­comes a sep­a­rate heap al­lo­ca­tion, and al­lo­ca­tors round up to the near­est size class. Big Pineapple uses je­mal­loc, an al­lo­ca­tor de­signed for mul­ti­threaded, al­lo­ca­tion-heavy work­loads. je­mal­loc groups al­lo­ca­tions of sim­i­lar sizes into fixed-size bins. A TXT record re­quests 32 bytes and fits ex­actly into a 32-byte bin, wast­ing noth­ing, but an MX record re­quests 40 bytes and rounds up to 48, wast­ing 8 bytes.

The sec­ond cost is poor mem­ory lo­cal­ity. Without box­ing, the record enum val­ues for a cache en­try sit in a sin­gle con­tigu­ous al­lo­ca­tion. With box­ing, data for each boxed vari­ant lives in a sep­a­rate heap re­gion. Reading it re­quires fol­low­ing a pointer, and when that pointer lands far from the rest of the en­try, the CPU has to fetch a new cache line. With mil­lions of cache en­tries, boxed data ends up scat­tered across the heap rather than packed to­gether.

Neither cost is cat­a­strophic on its own, but elim­i­nat­ing both, as the next sec­tion shows, yields a mea­sur­able im­prove­ment in both mem­ory us­age and lookup la­tency.

Storing records in wire for­mat

An ob­vi­ous next step would be to store the full DNS re­sponse in wire for­mat, patch­ing only per-client fields like the mes­sage ID on each lookup. But this has draw­backs. DNSSEC records are only in­cluded when the client sets the DO (DNSSEC OK) flag. Storing a com­plete wire for­mat mes­sage means ei­ther caching two vari­ants, one with DNSSEC and one with­out, or fil­ter­ing them out of an al­ready-built mes­sage. There is also a cost to pars­ing the full mes­sage on every lookup, which the enum ap­proach we just de­scribed avoids by stor­ing al­ready-parsed records.

As a mid­dle ground, we store just the record data as raw bytes, while keep­ing the rest of the cache en­try as struc­tured fields. Instead of a list of parsed enum vari­ants, we store the records as a sin­gle Box<[u8]> con­tain­ing each record en­coded as a 2-byte length pre­fix fol­lowed by its raw bytes.

This elim­i­nates the per-vari­ant enum over­head and the boxed heap al­lo­ca­tions from the pre­vi­ous op­ti­miza­tion. The data also be­comes packed con­tigu­ously, which im­proves CPU cache lo­cal­ity. The trade­off is that records can no longer be ran­domly in­dexed. We have to it­er­ate through the buffer se­quen­tially. This adds some com­plex­ity for fea­tures like round-robin ro­ta­tion of A/AAAA records, but since record counts per en­try are small, the cost is neg­li­gi­ble.

When build­ing a DNS re­sponse from cached records, most record types can be copied di­rectly from the buffer into the out­go­ing mes­sage. Previously, each parsed record had to be se­ri­al­ized field by field back into DNS wire for­mat. The new lay­out skips that work for A, AAAA, TXT, and all DNSSEC record types by copy­ing their en­coded bytes di­rectly. Only records con­tain­ing do­main names, such as CNAME, NS, MX, and SOA, still re­quire pars­ing so we can ap­ply DNS name com­pres­sion. Since records that sup­port di­rect copy­ing make up the vast ma­jor­ity of our traf­fic, this change re­duces work on the lookup path. Combined with im­proved mem­ory lo­cal­ity, this re­duced cache lookup la­tency by 5% in our bench­marks.

To build the record data buffer, we write into a reusable scratch­space buffer that per­sists across cache in­ser­tions. Since pre­vi­ous writes have al­ready grown it, the buffer rarely needs to be re­al­lo­cated. Records vary in size, so we do not know the ex­act buffer size un­til they have been se­ri­al­ized. Once the records are in the scratch­space buffer, we al­lo­cate a Box<[u8]> and mem­cpy the data into it. This re­places the sep­a­rate al­lo­ca­tion for each boxed record with one al­lo­ca­tion for all record data. It also avoids the waste from shrink­ing a Vec<u8>, where the al­lo­ca­tor may not be able to re­claim the un­used tail of the orig­i­nal al­lo­ca­tion. In our bench­mark, this change alone in­creased cache in­sert through­put by 13%.

The re­sults

The pro­duc­tion mea­sure­ments show how the bench­marked per-en­try sav­ings trans­lated to whole-process res­i­dent mem­ory. The graph be­low shows p90, p98, and p99 mem­ory us­age across Big Pineapple in­stances. The first dashed line marks the start of the roll­out on May 18, 2026, and the sec­ond marks its com­ple­tion across all ser­vices on July 6, 2026. Each re­lease in­tro­duced one or more of the op­ti­miza­tions de­scribed above, so mem­ory us­age dropped in steps rather than all at once.

As each re­lease rolled out, restarted in­stances be­gan with empty caches and con­sumed more mem­ory as those caches filled. The sta­ble plateaus there­fore rep­re­sent steady-state mem­ory us­age bet­ter than the ini­tial dips.

Per-instance mem­ory us­age dropped across all per­centiles. At p99, mem­ory dropped from 9.3 GB to 5.3 GB, a 43% re­duc­tion in res­i­dent mem­ory. At p90, mem­ory dropped from 6.5 GB to 3.8 GB, a 42% re­duc­tion. Instances with fuller caches saw the largest ab­solute sav­ings.

In our bench­marks, these five op­ti­miza­tions re­duced the per-en­try mem­ory foot­print from 953 bytes to 420 bytes, a 56% re­duc­tion. Per-entry al­lo­ca­tions dropped from 1.1 KB to 461 bytes. The re­duc­tions mea­sured in pro­duc­tion are smaller be­cause res­i­dent mem­ory in­cludes the cache along­side all other process data. After the roll­outs set­tled, ag­gre­gate work­ing-set mem­ory across the fleet was roughly 100 ter­abytes lower.

Performance also im­proved. Cache in­sert through­put in­creased by 43%, while lookup la­tency dropped by 19%.

Metric

Before

After

Change

Per-entry net foot­print

953 bytes

420 bytes

-56%

Per-entry al­lo­ca­tions

1.1 KB

461 bytes

-58%

Cache in­sert through­put

625,000 en­tries/​s

893,000 en­tries/​s

+43%

Cache lookup la­tency

828 ns

670 ns

-19%

We plan to rein­vest the freed mem­ory into in­creas­ing cache ca­pac­ity with­out in­creas­ing our mem­ory us­age, which im­proves cache hit rates and re­duces up­stream query vol­ume. We’re also ex­plor­ing fur­ther op­ti­miza­tions to the cache it­self.

To learn more about Big Pineapple, see How Rust and Wasm power Cloudflare’s 1.1.1.1. If you work on DNS or other large sys­tems, share the op­ti­miza­tions that have worked for you in the Cloudflare Community or on the Cloudflare Developers Discord.

Microduck - A tiny biped robot you can teach new tricks | Pollen Robotics

pollen-robotics.com

MicroduckMade to move · Ready to learn

A 25 cm open-source biped you train your­self with re­in­force­ment learn­ing. Playable out of the box.

Pre-order for $399

The launch film · sound on

Roll thetape

Waking up the duck…

Meet the twin

sim2re­althat works

Trained in sim, de­ployed on the real ro­bot. This is the sim­u­lated twin the ducks were trained on.

Fun out of the box. Yours to re­train.

Teach it newtricks

Every be­hav­iour is a pol­icy you can re­train on your own ma­chine.

01

Train in sim­u­la­tion

Behaviours are learned in physics sim, on your ma­chine or on Hugging Face Jobs.

02

Deploy on the ro­bot

One step from sim­u­la­tion to the real thing.

03

Refine the sim­u­la­tion

Tune, re-train, re-de­ploy.

04

Publish the pol­icy

Share your new be­hav­ior with the com­mu­nity!

Walk

Velocity-tracking gait.

Sit & stand

Sits down, holds the pose, stands back up on its own.

Kick

A one-shot boot, then straight back to walk­ing.

Grab

Dips the beak to the ground, scoops, and pops back up­right.

Roller skat­ing

Roller skat­ing lo­co­mo­tion when the skates are equipped.

Get back up

Flat on its back to stand­ing, all by it­self, ready for the next com­mand.

One ro­bot, four colour­ways

Choose your­colour

Every Microduck ships in one of four colour­ways. Same ro­bot, same brains un­der­neath - pick the shell that best fits you.

Waking up the duck…

Out in the world

In the wild

The real ro­bot in real places - on desks, on the pitch, out at golden hour.

The ro­bot, and what to add to it

Pick your­pack

The ro­bot is every­thing you need on day one. The packs add play gear and spare parts.

$399

The ro­bot

Microduck

In the box

Robot, bat­tery, USB-C ca­ble, game con­troller.

$39

Dual charger, 2x bat­ter­ies.

$119

3x spare mo­tors, 5x mo­tor ca­bles, 2x bat­ter­ies, dual charger, 10x NFC tags, Hugging Face credit, screw­driver, screw pack.

$39

Laser pointer, NFC po­laroid, 2x rollers, ball, 10x NFC tags.

Built in the open

Open source

The SDK, the sim­u­la­tion and the full RL train­ing stack are on GitHub. What the ro­bot runs is what you can read, fork and re­train.

pollen-ro­bot­ics/​mi­cro­duck

ssh mi­cro­duck

$ ro­botctl mon­i­tor # sta­tus of the ro­bot$ ro­botctl con­fig­ure # con­fig­ure the ro­bot$ ro­botctl up­date # up­date the ro­bot

Apache-2.0

The whole soft­ware stack, per­mis­sively li­censed

MuJoCo

The physics sim every pol­icy is trained in

7 poli­cies

Every shipped move, pub­lished and re­train­able

Join the flock

Builds on show, poli­cies to swap, help when a leg does some­thing strange. The com­mu­nity lives on Discord.

End of tape · be kind, rewind

Pre-orders are­open now

Pre-order for $399

In four colour­ways. Ships be­fore Christmas 2026.Introductory price, be­fore taxes and ship­ping.

Small Models Have Arrived

calv.info

For the past few weeks, I’ve been play­ing with gpt-5.6-luna. It is shock­ingly ca­pa­ble, fast, and smart. I reg­u­larly see it do ~100 tps, and rip around my code­base, email, and knowl­edge base.

Of course, the biggest thing with luna is the cost. I’ve tried run­ning some fairly com­pli­cated re­search threads, and it’s pretty tough to run up a large bill. Even hav­ing it search across thou­sands of emails, I end up with an API cost in the tens of cents.

With GLM 5.3, we even have a new op­tion at the Pareto fron­tier.

When do­ing cod­ing work, I al­most al­ways reach for the most ex­pen­sive and ca­pa­ble mod­els (Fable 5, 5.6 Sol). So it’s been easy to miss the progress the small fast mod­els have made.

One thing a few in­vestors I’ve talked with have men­tioned: It’s weird we’re not see­ing more con­sumer AI com­pa­nies. Why is that?”

There’s a straight­for­ward an­swer: to­ken costs.

In the times be­fore AI, the play­book for big con­sumer apps looked like this…

cre­ate some sort of com­pelling web­site which is fairly cheap to run

at­tract a bunch of users (typically with some vi­ral­ity)

raise money, scale to more users

cre­ate an ads mar­ket­place

This roughly de­scribes most of the big con­sumer com­pa­nies (Google, Facebook, Snapchat, etc.).1

But what if you want to add AI to your prod­uct? Well, now you have some real in­fer­ence costs on every re­quest! Suddenly the amount of cap­i­tal re­quired in­creases dra­mat­i­cally.

A pet eval of mine is to build a daily news site, per­son­al­ized to me:

re­search @calvinfo on the in­ter­net. fig­ure out what news they might like. build a mi­cro-site with to­day’s top sto­ries, per­son­al­ized for them. search hn, red­dit, twit­ter, etc.

re­search @calvinfo on the in­ter­net. fig­ure out what news they might like. build a mi­cro-site with to­day’s top sto­ries, per­son­al­ized for them. search hn, red­dit, twit­ter, etc.

With the pre­vi­ous gen­er­a­tion of mod­els (Sonnet class), you’d spend ~$1 to get any­where. Charging $30/mo is un­ten­able for a con­sumer app. There’s ob­vi­ously a lot we can op­ti­mize here, but if you’re charg­ing what the WSJ or The Economist charges, you’d bet­ter be de­liv­er­ing sim­i­lar value.

But look­ing at luna, the re­sults are pretty de­cent, and the av­er­age cost is ~$0.10. Now we’re talk­ing!

Where I think this gets even more in­ter­est­ing is in the world of busi­ness.

My Segment co-founder Peter and I were re­cently com­par­ing notes on a hike. Across his var­i­ous star­tups, Peter has seen two kinds of work:

the IQ 180” work. some mad sci­en­tist ge­nius type comes up with some crazy so­lu­tion you’ve never thought of.

the token spewer” work. be­ing ul­tra re­spon­sive, push­ing the ball for­ward across dozens of dif­fer­ent fronts.

Peter runs mul­ti­ple com­pa­nies. Beyond Segment, he’s raised $100m+ for Charm Industrial, and just re­cently closed a Series A for Revoy. He’s in­cred­i­bly or­ga­nized and ef­fi­cient with his time.

And yet, Peter men­tioned that ~95% of the work he does falls into bucket 2. It’s hop­ping on calls. Nudging peo­ple. Blocking and tack­ling.

To be clear, Peter says his com­pa­nies would be dead-in-the-wa­ter to­day with­out an IQ 180 tech­ni­cal mind solv­ing the deep prob­lems. Just that most of his work falls in bucket 2.2

I think de­mand for frontier-level” mod­els is go­ing to keep com­pound­ing. Especially for fields that re­quire novel break­throughs or dis­cov­ery (engineering, hard sci­ence, model train­ing).

But I also think the de­mand for fast/cheap/good-enough” mod­els is just about to take off.

Think of the peo­ple you in­ter­act with on a daily ba­sis: cowork­ers, ven­dors, and cus­tomers. Nine times out of ten, you want some­one who is su­per re­spon­sive, and just han­dles things for you. Most of the human to­kens” at com­pa­nies to­day are spent this way — hir­ing skews heav­ily to­ward the fast/​cheap/​good-enough ar­che­type.

There’s a lot of work that needs to hap­pen to make fast/​cheap/​good-enough mod­els a re­al­ity for busi­ness. New har­nesses, prompt in­jec­tion safety, roles, and per­mis­sions. But I’m con­fi­dent we’ll fig­ure that out.

If you’re also ex­per­i­ment­ing with mak­ing small mod­els use­ful, please drop me a line.

Footnotes

Amazon and Netflix are the no­table ex­cep­tions ↩

Amazon and Netflix are the no­table ex­cep­tions ↩

Peter is also be­ing mod­est here. He’s sharp as a tack. ↩

Peter is also be­ing mod­est here. He’s sharp as a tack. ↩

507 Mechanical Movements

507movements.com

Wait… you said they were an­i­mated!

Ah, yes… well, un­for­tu­nately we do not have all the an­i­ma­tions work­ing yet, but we do have quite a few.

Look for the color thumb­nails. They iden­tify the com­pleted an­i­ma­tions. Use the prev and next links (above right) to browse the thumb­nail pages.

As time goes on, we’ll be adding more un­til all 507 are com­plete. Click the Facebook Subscribe” or Twitter Follow” but­ton be­low to be no­ti­fied of our progress.

Meanwhile, we hope you en­joy the an­i­ma­tions we have com­pleted, along with Henry T. Brown’s orig­i­nal il­lus­tra­tions in this clas­sic tech­ni­cal ref­er­ence.

See the About page for more.

Close

The load-bearing vocabulary of Claude

louisabraham.github.io

Trade

xkcd.com

Comics I en­joy: Three Word Phrase, SMBC, Dinosaur Comics, Oglaf (nsfw), A Softer World, Buttersafe, Perry Bible Fellowship, Questionable Content, Buttercup Festival, Homestuck, Junior Scientist Power Hour

xkcd.com is best viewed with Netscape Navigator 4.0 or be­low on a Pentium 3±1 em­u­lated in Javascript on an Apple IIGSat a screen res­o­lu­tion of 1024x1. Please en­able your ad block­ers, dis­able high-heat dry­ing, and re­move your de­vice­from Airplane Mode and set it to Boat Mode. For se­cu­rity rea­sons, please leave caps lock on while brows­ing.

Access Denied

www.gatesnotes.com

Reference #18.6c24c317.1787899885.37c03da

https://​er­rors.edge­suite.net/​18.6c24c317.1787899885.37c03da

Intelligent transcription with Gemini 3.5 Transcribe

blog.google

Aug 26, 2026

|

Our lat­est speech-to-text model de­signed for pre­cise and in­tel­li­gent real-time tran­scrip­tion.

Diego Melendo Casado

Senior Director, Engineering, Gemini Audio

Luke Leonhard

Chief of Staff, Gemini Audio, on be­half of Gemini Audio Team

Your browser does not sup­port the au­dio el­e­ment.

Listen to ar­ti­cle

[[duration]] min­utes

This con­tent is gen­er­ated by Google AI. Generative AI is ex­per­i­men­tal

Today, we’re in­tro­duc­ing Gemini 3.5 Transcribe, our most pre­cise speech-to-text model yet, de­signed for in­tel­li­gent voice in­ter­ac­tions. Unlike con­ven­tional speech recog­ni­tion mod­els that strug­gle with back­ground noise, com­plex jar­gon, and dis­flu­ency cleanup, Gemini 3.5 Transcribe con­verts raw au­dio di­rectly into ac­cu­rate, pol­ished, for­mat­ted text.

Across our prod­ucts like the Gemini app and on Android, we’ve seen con­sumers al­ready ben­e­fit­ing from this tran­scrip­tion model with new voice ca­pa­bil­i­ties like Rambler on Android and in the Gemini app on ma­cOS. Now, de­vel­op­ers can build sim­i­lar ca­pa­bil­i­ties with Gemini 3.5 Transcribe in the Gemini API in Google AI Studio and Gemini Enterprise Agent Platform.

We’ve built 3.5 Transcribe to plug seam­lessly into your de­vel­oper work­flows, whether you’re build­ing voice agents, real-time cap­tion­ing tools, or post-call an­a­lyt­ics pipelines. The model is avail­able across two sep­a­rate APIs:

Real-time stream­ing: Delivers con­tin­u­ous, bidi­rec­tional stream­ing with sub-sec­ond la­tency for in­ter­ac­tive voice apps via the Live API us­ing gem­ini-3.5-tran­scribe-live.

Pre-recorded au­dio pro­cess­ing: Transcribes recorded au­dio, meet­ings, call logs, and more with speaker at­tri­bu­tion and word-level time­stamps via the Interactions API us­ing gem­ini-3.5-tran­scribe.

Get more pre­cise and in­tel­li­gent tran­scrip­tion

Gemini 3.5 Transcribe is de­signed to cap­ture your nat­ural speak­ing style to bet­ter un­der­stand your in­tent and rec­og­nize cus­tom vo­cab­u­lary, so you can ex­e­cute tasks with your voice.

Smart tran­scrip­tion: Seamlessly han­dles self-cor­rec­tions (like let’s meet Tuesday—no, Wednesday”), re­moves filler words (“ums” and “ahs”), auto-for­mats your text.

Function call­ing: The model can del­e­gate com­plex tasks (such as im­age gen­er­a­tion and file analy­sis) to other Gemini mod­els via func­tion calls. Currently avail­able in the Gemini ma­cOS app.

More pre­cise tran­scrip­tion: As mea­sured by Artificial Analysis, achieves an av­er­age Word Error Rate (WER) of 4.0% for stream­ing and 2.6% for non-stream­ing use-cases. It shows strong per­for­mance across noisy, real-world en­vi­ron­ments, ac­cu­rately cap­tur­ing al­phanu­meric en­ti­ties like postal codes and or­der IDs.

Custom vo­cab­u­lary: Recognizes spe­cial­ized jar­gon and unique spellings by seam­lessly adapt­ing tran­scrip­tions to your pro­vided cus­tom vo­cab­u­lary.

Global lan­guage sup­port: Automatically de­tects and tran­scribes over 85 lan­guages, seam­lessly han­dling re­gional ac­cents and di­verse di­alects.

Multi-speaker iden­ti­fi­ca­tion: Accurately at­trib­utes speech in pre-recorded au­dio with time­stamps for up to three speak­ers (support for 3+ speak­ers is ex­per­i­men­tal).

Gemini 3.5 Transcribe’s per­for­mance rep­re­sents a ma­jor ad­vance­ment from our pre­vi­ous tran­scrip­tion model, Chirp 3, of­fer­ing new ca­pa­bil­i­ties, im­proved word er­ror rates, and sig­nif­i­cantly bet­ter la­tency. As mea­sured by Artificial Analysis, time to fi­nal tran­scrip­tion, for ex­am­ple, im­proves by 70%. On the FLEURS bench­mark across a set of top lan­guages and lo­cales, the model de­liv­ers pre­cise mul­ti­lin­gual per­for­mance, im­prov­ing over Chirp 3, and achiev­ing a 5.50% WER in stream­ing mode and 5.04% WER in non-stream­ing use-cases.

Experience smart tran­scrip­tion and ad­vanced dic­ta­tion

In ad­di­tion to the Gemini API in the Google AI Studio and Gemini Enterprise Agent Platform, 3.5 Transcribe goes fur­ther than stan­dard speech-to-text to make work­ing across Google feel more nat­ural and in­tu­itive. By bring­ing con­text-aware un­der­stand­ing di­rectly into every­day sur­faces like Gboard, Antigravity, the Gemini app, and Chrome, it cap­tures nu­ances, in­tent, and in­line ed­its with ease.

On Gboard on Android, through the new Rambler fea­ture, 3.5 Transcribe trans­forms spo­ken thoughts into well-for­mat­ted text, fil­ter­ing out filler words. You can also use your voice to make ed­its, cor­rect mis­spellings, and change the writ­ing style.

On Google Antigravity, 3.5 Transcribe pairs screen con­text and chat his­tory, with your per­mis­sion, to en­sure pin­point tran­scrip­tion ac­cu­racy across file names, agent thoughts, and ac­tive doc­u­ments.

In Google AI Studio, you can ac­cess 3.5 Transcribe in Build mode to vibe code apps with your voice on the fly.

In the Gemini app on ma­cOS, 3.5 Transcribe not only tran­scribes your free nat­ural speech into clean for­mat­ted text, but also en­ables voice com­mands that can pair seam­lessly with screen con­text to power com­plex work­flows. By call­ing on other Gemini mod­els in the back­ground to han­dle the heavy lift­ing, the model makes it ef­fort­less to sum­ma­rize lo­cal files, re­pur­pose text across apps, or gen­er­ate im­ages right at your cur­sor—us­ing just your voice.

Coming soon to Chrome, you’ll be able to talk to type in any web field — mak­ing it ef­fort­less to dic­tate replies, draft posts, or prompt Gemini in Chrome more nat­u­rally and eas­ily with your voice.

Read the early re­views

By lever­ag­ing the Gemini Live API, de­vel­oper plat­forms such as Agora, Fishjam, LangChain, LiveKit, Pipecat, Vercel, and Vision Agents en­able de­vel­op­ers to build and de­ploy high-per­for­mance voice-dri­ven in­ter­faces with ease. These plat­forms man­age com­plex real-time me­dia stream­ing in­fra­struc­ture be­hind the scenes, al­low­ing de­vel­op­ers to fo­cus en­tirely on craft­ing the user ex­pe­ri­ence.

Companies like vivo, Intellitek Health, and Lingopal have also shared pos­i­tive feed­back on 3.5 Transcribe, high­light­ing its im­pres­sive la­tency, ac­cu­racy, and ex­pan­sive lan­guage sup­port.

Start us­ing 3.5 Transcribe to­day

For de­vel­op­ers: In pub­lic pre­view in the Gemini API via Google AI Studio and Google Antigravity.

For en­ter­prises: In pub­lic pre­view via Gemini Enterprise Agent Platform and com­ing soon to Gemini Enterprise for Customer Experience.

For every­one: In Gemini app on ma­cOS in English, Rambler on Android in se­lect coun­tries and lan­guages, and com­ing soon to Chrome.

Get the lat­est news from Google in your in­box

Sign up for our newslet­ters with prod­uct up­dates, event in­for­ma­tion, spe­cial of­fers, and more.

Your in­for­ma­tion will be used in ac­cor­dance with Google’s pri­vacy pol­icy. You may opt out at any time.

Gemini Omni 1.1 Flash lets you build with more control

blog.google

Aug 27, 2026

|

Omni now de­liv­ers stu­dio-qual­ity video pro­duc­tion, in­clud­ing the abil­ity to ex­tend a scene, first and last frame in­ter­po­la­tion, crisp 4K up­scal­ing, faster pro­to­typ­ing, and more.

Anish Nangia

Product Manager, Google DeepMind

Alisa Fortin

Product Manager, Google DeepMind

Your browser does not sup­port the au­dio el­e­ment.

Listen to ar­ti­cle

[[duration]] min­utes

This con­tent is gen­er­ated by Google AI. Generative AI is ex­per­i­men­tal

Today, we’re in­tro­duc­ing Gemini Omni 1.1 Flash, a new suite of cre­ative con­trols and gen­er­a­tive video ca­pa­bil­i­ties to sup­port de­vel­op­ers. Gemini Omni brought real-world rea­son­ing to gen­er­a­tive cre­ation, and to­day’s up­dates make Omni 1.1 pro­duc­tion-ready for pro­fes­sional use via the Gemini API in Google AI Studio.

Whether you’re build­ing gen­er­a­tive video work­flows, cre­ative tools, or me­dia edit­ing soft­ware, these up­dates make gen­er­a­tive video more con­trol­lable, faster to it­er­ate on, and pol­ished for real-world de­ploy­ment. Here’s a look at what’s new:

Extend scenes for longer sto­ry­telling

Scene ex­ten­sion al­lows you to take an ex­ist­ing video and con­tinue gen­er­at­ing footage seam­lessly from where it left off.

With Omni 1.1, the model can now an­a­lyze up to 10 sec­onds of prior con­text — a leap from pre­vi­ous mod­els that only ref­er­enced the fi­nal sec­ond. The re­sult is im­proved vi­sual con­sis­tency and nar­ra­tive ad­her­ence, let­ting you build longer sto­ries or branch into new cre­ative di­rec­tions. You can ex­tend videos in 10-second in­cre­ments up to a to­tal cu­mu­la­tive length of 40 sec­onds.

Here’s how you can ex­tend your scene with the Gemini API:

from google im­port genai

client = genai.Client()

in­ter­ac­tion = client.in­ter­ac­tions.cre­ate( model=“gem­ini-omni-1.1-flash”, pre­vi­ous_in­ter­ac­tion_id=pre­vi­ous_video_in­ter­ac­tion.id, in­put=[ {“type”: text”, text”: Continue the scene.“} ], re­sponse_­for­mat={ resolution”: 360p”, }, )

Specify first and last frames

Achieve smooth tran­si­tions and cam­era move­ments by spec­i­fy­ing the start­ing and end­ing frames of a shot. Omni 1.1 gen­er­ates con­tin­u­ous video be­tween two keyframes, mak­ing it ideal for com­plex cam­era or­bits, zoom tran­si­tions, or seam­less loop­ing clips.

Prompt 1: A close-up low-an­gle shot of a styl­ish drum­mer in a beige suit play­ing a red drum kit in a grand hall tran­si­tions as the cam­era whip-pans to the side, re­veal­ing an older sax­o­phon­ist play­ing along­side a bal­let dancer spin­ning in a white out­fit un­der soft pur­ple stage lights. One con­tin­u­ous shot, no jump cuts.

Prompt 2: The cam­era zooms into the TV screen, where we see the same woman and the same scene from the be­gin­ning. Seamless video. One con­tin­u­ous shot, no jump cuts.

Draft videos more ef­fi­ciently in 360p

Generate light­weight pre­views in 360p res­o­lu­tion up to 60% faster* and at a third of the cost com­pared to Omni 1.1’s stan­dard 720p res­o­lu­tion. This is help­ful for rapid pro­to­typ­ing, sto­ry­board it­er­a­tion, and quick ren­der­ing in de­vel­oper plat­forms.

*Up to 60% faster gen­er­a­tion based on sys­tem through­put of 360p vs. 720p res­o­lu­tion

Prompt: A mi­cro­scopic view of iri­des­cent ma­rine di­atoms, dis­play­ing in­tri­cate, glass-like sil­ica shells with breath­tak­ing nat­ural sym­me­try. The col­ors range from deep vol­canic am­ber and warm cop­per to vi­brant turquoise and vi­o­let, mim­ic­k­ing the rich palette of earth and ocean. Tiny, del­i­cate struc­tures glow softly against a clean dark field back­ground. High-fidelity sci­en­tific imag­ing, sharp de­tails, or­ganic tex­tures, mi­cro-pho­tog­ra­phy. Maintain the mi­cro­scope lens ef­fect through­out the en­tire video.

Upscale up to 4K res­o­lu­tion

Generate pol­ished, high-res­o­lu­tion 1080p or 4K out­puts that are ready for pro­fes­sional pro­duc­tion with Omni 1.1.

Prompt 1: Fish swim­ming, track­ing shot

Prompt 2: A lit­tle chip­munk dart­ing out of the woods from the left side of the screen and sniff­ing the air in­quis­i­tively be­fore dart­ing out of frame on the right side

Prompt 3: Cinematic macro close-up of vi­brant golden-or­ange Japanese maple leaves on a del­i­cate branch, gen­tly rustling and sway­ing in a soft, rhyth­mic au­tumn breeze. Sunlight fil­ters through the translu­cent fo­liage, cre­at­ing a warm, glow­ing ef­fect. Shallow depth of field, dreamy bokeh back­ground, hy­per-de­tailed tex­tures, pho­to­re­al­is­tic, 4k.

Add video ref­er­ences in your mul­ti­modal in­put

Reference up to three sec­onds of video when craft­ing your scene, al­low­ing you to main­tain vi­sual con­text and char­ac­ter con­sis­tency based on video ref­er­ences.

Prompt: Use the three up­loaded videos of dancers and re­place them with the pro­vided char­ac­ters. Have them per­form their in­di­vid­ual dances from the ref­er­ence videos, all to­gether in the large, open space from the pro­vided im­age.

The dog char­ac­ter dog.png should do the clas­si­cal dance from dance3.mp4. The oc­to­pus octo.png should do the hip hop dance from dance1.mp4, and the bear bear.png should do the break­dance from dance2.mp4. The fi­nal re­sult should be one con­tin­u­ous shot with no scene cuts.

Inspiring con­cepts for what you can build

Here are a few ideas show­ing how de­vel­op­ers can put these new ca­pa­bil­i­ties into ac­tion across cus­tom tools and cre­ative work­flows.

See how cus­tomers are putting Omni Flash in pro­duc­tion

Our cus­tomers are al­ready dri­ving real-world pro­duc­tion with Gemini Omni Flash via the Agent Platform API. Explore the videos they’ve cre­ated and hear about how they are us­ing the model be­low.

Build with Gemini Omni 1.1 Flash Today

Pricing table for Gemini Omni 1.1 Flash.

Omni 1.1 is rolling out across the Google de­vel­oper ecosys­tem:

Start build­ing in Google AI Studio: Try out Omni 1.1 di­rectly in Google AI Studio.

Build on Gemini Enterprise Agent Platform: Enterprises can build with Omni 1.1 di­rectly via Agent Platform API.

Explore the de­vel­oper doc­u­men­ta­tion: Check out the of­fi­cial doc­u­men­ta­tion, the cook­book and prompt­ing guides to learn how to in­te­grate scene ex­ten­sions, video ref­er­ences, and up­scal­ing into your ap­pli­ca­tions.

Omni 1.1 is also avail­able to all Google AI Plus, Pro and Ultra sub­scribers glob­ally in Google Flow, start­ing to­day. Scene ex­ten­sion is avail­able to all Google AI Plus, Pro and Ultra sub­scribers glob­ally in the Gemini app.

Get the lat­est news from Google in your in­box

Sign up for our newslet­ters with prod­uct up­dates, event in­for­ma­tion, spe­cial of­fers, and more.

Your in­for­ma­tion will be used in ac­cor­dance with Google’s pri­vacy pol­icy. You may opt out at any time.

Integer Divide-by-Zero in `vpk_read_packet` (VPK Demuxer)

code.ffmpeg.org

Hello.

This is a bug found with our fuzzer: https://​github.com/​daedalus/​fuzzer/

File: libav­for­mat/​vpk.c:89 Severity: Medium — crafted 21-byte in­put crashes any FFmpeg-based ap­pli­ca­tion that opens a ma­li­cious .vpk file or stream Root cause: vp­k_read­_­packet di­vides vpk->last_block­_­size by par->ch_lay­out.nb_chan­nels with­out check­ing whether nb_chan­nels is zero. A mal­formed VPK header can set nb_chan­nels = 0, caus­ing SIGFPE on the di­vi­sion.

Description

The Sony PS2 VPK de­muxer (libavformat/vpk.c) reads au­dio blocks from a cus­tom con­tainer for­mat. In vp­k_read­_­packet, the last block of the stream is han­dled spe­cially:

if (vpk->current_block == vpk->block­_­count) { un­signed size = vpk->last_block­_­size / par->ch_lay­out.nb_chan­nels; un­signed skip = (par->block_align - vpk->last_block­_­size) / par->ch_lay­out.nb_chan­nels; … }

Both size and skip di­vide by par->ch_lay­out.nb_chan­nels. When nb_chan­nels is zero, the CPU raises SIGFPE (integer di­vide-by-zero ex­cep­tion).

Trigger Chain

Demuxer probe (vpk_probe) matches the VPK big-en­dian magic and as­signs the in­put to the VPK de­muxer.

vp­k_read­_­header parses the 24-byte header. The fuzz in­put sets nb_chan­nels = 0 at header bytes 0x0e–0x11. vp­k_read­_­header does val­i­date nb_chan­nels > 0, but in the fuzzer’s cus­tom-AVIO path the probe/​header data and the later packet-read data can di­verge: by the time vp­k_read­_­packet runs, par->ch_lay­out.nb_chan­nels has re­verted to 0 from the orig­i­nal fuzz stream while vpk->last_block­_­size and vpk->block­_­count were com­puted from probe data with a valid chan­nel count. The di­vi­sion is there­fore reached with a live-but-zero di­vi­sor.

vp­k_read­_­packet reaches the fi­nal-block branch and di­vides by zero on both size and skip.

Crash Input

Hex dump of the 21-byte crash in­put (crash_1787378545_34bc062c_sig_signal8.bin):

00000000 20 4b 50 56 56 50 00 f8 04 00 3b 03 61 39 56 32 | KPVVP….;.a9V2| 00000010 36 36 30 38 50 |6608P|

Bytes 0 – 3: 20 4b 50 56 — ASCII KPV, which is the VPK big-en­dian magic VPK byte-re­versed across a word bound­ary

Byte 0x0e–0x11: 00 00 00 00 — nb_chan­nels = 0, the crash trig­ger

GDB Backtrace

Program re­ceived sig­nal SIGFPE, Arithmetic ex­cep­tion. 0x00005555557a9877 in vp­k_read­_­packet (s=0x555557fed700, pkt=0x555557fed300) at libav­for­mat/​vpk.c:89 89 un­signed size = vpk->last_block­_­size / par->ch_lay­out.nb_chan­nels;

#0 vp­k_read­_­packet #1 ff_read­_­packet #2 read­_frame_in­ter­nal #3 av_read­_frame #4 fuz­z_ffm­peg #5 main

Crash Metadata

Exploitability Assessment

The di­vide-by-zero is a de­nial-of-ser­vice prim­i­tive. There is no con­trolled write or ar­bi­trary read ad­ja­cent to the fault­ing in­struc­tion. The in­put can be em­bed­ded in a .vpk file or a con­tainer that iden­ti­fies it­self as VPK to trig­ger the crash in any FFmpeg-linked ap­pli­ca­tion.

Suggested Fix

Add a guard at the top of vp­k_read­_­packet to re­ject zero-chan­nel streams cleanly:

sta­tic int vp­k_read­_­packet(AV­For­mat­Con­text *s, AVPacket *pkt) { AVCodecParameters *par = s->streams[0]->codec­par; VPKDemuxContext *vpk = s->priv_­data; int ret, i;

if (par->ch_layout.nb_channels == 0) re­turn AVERROR_INVALIDDATA;

vpk->cur­ren­t_block++; … }

This is con­sis­tent with the ex­ist­ing val­i­da­tion in vp­k_read­_­header (if (st->codecpar->ch_layout.nb_channels <= 0) re­turn AVERROR_INVALIDDATA;) and re­turns a clean er­ror in­stead of SIGFPE.

Regression Test

/* Trigger: 21-byte VPK stream with nb_chan­nels=0 — SIGFPE in vpk.c:89 */ sta­tic const un­signed char vp­k_crash[] = { 0x20, 0x4b, 0x50, 0x56, 0x56, 0x50, 0x00, 0xf8, 0x04, 0x00, 0x3b, 0x03, 0x61, 0x39, 0x56, 0x32, 0x36, 0x36, 0x30, 0x38, 0x50 };

/* Expect: av_read­_frame re­turns -22 (AVERROR_INVALIDDATA), does not crash */

Hello.

This is a bug found with our fuzzer: https://​github.com/​daedalus/​fuzzer/

**File**: `libavformat/vpk.c:89` **Severity**: Medium — crafted 21-byte in­put crashes any FFmpeg-based ap­pli­ca­tion that opens a ma­li­cious `.vpk` file or stream **Root cause**: `vpk_read_packet` di­vides `vpk->last_block_size` by `par->ch_layout.nb_channels` with­out check­ing whether `nb_channels` is zero. A mal­formed VPK header can set `nb_channels = 0`, caus­ing `SIGFPE` on the di­vi­sion.

### Description

The Sony PS2 VPK de­muxer (`libavformat/vpk.c`) reads au­dio blocks from a cus­tom con­tainer for­mat. In `vpk_read_packet`, the last block of the stream is han­dled spe­cially:

```c if (vpk->current_block == vpk->block­_­count) { un­signed size = vpk->last_block­_­size / par->ch_lay­out.nb_chan­nels; un­signed skip = (par->block_align - vpk->last_block­_­size) / par->ch_lay­out.nb_chan­nels; … } ```

Both `size` and `skip` di­vide by `par->ch_layout.nb_channels`. When `nb_channels` is zero, the CPU raises `SIGFPE` (integer di­vide-by-zero ex­cep­tion).

### Trigger Chain

1. **Demuxer probe** (`vpk_probe`) matches the `VPK ` big-en­dian magic and as­signs the in­put to the VPK de­muxer. 2. **`vpk_read_header`** parses the 24-byte header. The fuzz in­put sets `nb_channels = 0` at header bytes `0x0e`–`0x11`. `vpk_read_header` does val­i­date `nb_channels > 0`, but in the fuzzer’s cus­tom-AVIO path the probe/​header data and the later packet-read data can di­verge: by the time `vpk_read_packet` runs, `par->ch_layout.nb_channels` has re­verted to `0` from the orig­i­nal fuzz stream while `vpk->last_block_size` and `vpk->block_count` were com­puted from probe data with a valid chan­nel count. The di­vi­sion is there­fore reached with a live-but-zero di­vi­sor. 3. **`vpk_read_packet`** reaches the fi­nal-block branch and di­vides by zero on both `size` and `skip`.

### Crash Input

Hex dump of the 21-byte crash in­put (`crash_1787378545_34bc062c_sig_signal8.bin`):

``` 00000000 20 4b 50 56 56 50 00 f8 04 00 3b 03 61 39 56 32 | KPVVP….;.a9V2| 00000010 36 36 30 38 50 |6608P| ```

- Bytes 0 – 3: `20 4b 50 56` — ASCII `” KPV”`, which is the VPK big-en­dian magic `VPK ` byte-re­versed across a word bound­ary - Byte 0x0e–0x11: `00 00 00 00` — `nb_channels = 0`, the crash trig­ger

### GDB Backtrace

``` Program re­ceived sig­nal SIGFPE, Arithmetic ex­cep­tion. 0x00005555557a9877 in vp­k_read­_­packet (s=0x555557fed700, pkt=0x555557fed300) at libav­for­mat/​vpk.c:89 89 un­signed size = vpk->last_block­_­size / par->ch_lay­out.nb_chan­nels;

#0 vp­k_read­_­packet #1 ff_read­_­packet #2 read­_frame_in­ter­nal #3 av_read­_frame #4 fuz­z_ffm­peg #5 main ```

### Crash Metadata

| Field | Value | |–-|–-| | Signal | `SIGFPE` (returncode −8) | | Fault ad­dress / RIP | `0x7ffff48a66d7` (instruction it­self) | | RSP | `0x7fffffffcce0` | | Execs to find | 495,211 | | Corpus at find | 13,188 en­tries | | Elapsed | 10 h 43 m | | Parent seed | `36e65f4009ba0cab` | | Target SHA256 | `d704c2a52b21bd33` |

### Exploitability Assessment

| Factor | Assessment | |–-|–-| | Crash de­ter­min­ism | **Deterministic** — 21 bytes, sin­gle de­muxer code path | | Trigger depth | Shallow — `avformat_open_input` auto-de­tects for­mat from magic | | Preconditions | None — in­put is self-con­tained, no net­work, no heap setup | | Signal type | SIGFPE (integer di­vide-by-zero), not mem­ory cor­rup­tion | | Memory safety | No OOB read/​write, no use-af­ter-free, no NULL deref­er­ence | | Reach | Any ap­pli­ca­tion that calls `avformat_open_input` + `av_read_frame` on un­trusted data | | Severity | **Medium** — re­li­able DoS; not an im­me­di­ate code-ex­e­cu­tion prim­i­tive |

The di­vide-by-zero is a **denial-of-service** prim­i­tive. There is no con­trolled write or ar­bi­trary read ad­ja­cent to the fault­ing in­struc­tion. The in­put can be em­bed­ded in a `.vpk` file or a con­tainer that iden­ti­fies it­self as VPK to trig­ger the crash in any FFmpeg-linked ap­pli­ca­tion.

### Suggested Fix

Add a guard at the top of `vpk_read_packet` to re­ject zero-chan­nel streams cleanly:

```c sta­tic int vp­k_read­_­packet(AV­For­mat­Con­text *s, AVPacket *pkt) { AVCodecParameters *par = s->streams[0]->codec­par; VPKDemuxContext *vpk = s->priv_­data; int ret, i;

if (par->ch_layout.nb_channels == 0) re­turn AVERROR_INVALIDDATA;

vpk->cur­ren­t_block++; … } ```

This is con­sis­tent with the ex­ist­ing val­i­da­tion in `vpk_read_header` (`if (st->codecpar->ch_layout.nb_channels <= 0) re­turn AVERROR_INVALIDDATA;`) and re­turns a clean er­ror in­stead of `SIGFPE`.

### Regression Test

```c /* Trigger: 21-byte VPK stream with nb_chan­nels=0 — SIGFPE in vpk.c:89 */ sta­tic const un­signed char vp­k_crash[] = { 0x20, 0x4b, 0x50, 0x56, 0x56, 0x50, 0x00, 0xf8, 0x04, 0x00, 0x3b, 0x03, 0x61, 0x39, 0x56, 0x32, 0x36, 0x36, 0x30, 0x38, 0x50 };

/* Expect: av_read­_frame re­turns -22 (AVERROR_INVALIDDATA), does not crash */ ```

To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".

10HN is also available as an iOS App

If you visit 10HN only rarely, check out the the best articles from the past week.

Visit pancik.com for more.