BrickellTechnologies

Research

CVE-2026-110779 and CVE-2026-110780: Two Memory-Safety Bugs in Netatalk's Spotlight RPC

Elias Hasas · ·8 min read

Netatalk is the open-source AFP server that lets Linux and BSD machines act as file servers and Time Machine targets for Macs. Today the Netatalk team published two advisories for bugs we reported in its Spotlight search support:

  • CVE-2026-110779: a heap out-of-bounds read in sl_createCNIDArray(), caused by type confusion. CVSS 3.1 4.3 (Medium).
  • CVE-2026-110780: a NULL-pointer dereference in three Spotlight RPC handlers. CVSS 3.1 5.3 (Medium).

Both affect Netatalk 3.1.0 through 4.5.1, and 4.5.2 fixes both. Neither gives an attacker code execution or a memory write. They’re worth a post anyway, because they sit in the same code that produced a type-confusion CVE in 2023, and the way we found them applies well beyond Netatalk.

Who is affected

You’re affected if you run Netatalk 3.1.0 through 4.5.1 with Spotlight support built in and enabled. Check your version with:

afpd -V

Whether Spotlight is enabled depends on the version. Through 4.4.x it was off unless afp.conf set spotlight = yes. From 4.5.0 it’s on by default, so anyone who moved to 4.5.0 or 4.5.1 without touching that setting has it enabled.

An attacker needs to be able to log in and open a volume. If you’ve enabled the guest UAM and have a volume guests can open, no credentials are needed.

Where Spotlight requests go

When a Mac runs a Finder search on a network volume, it sends the server an FPSpotlightRPC request carrying a marshalled query. Netatalk decodes that into a tree of typed values (a DALLOC_CTX) with sl_unpack_len(), then hands the tree to one of the sl_rpc_* handlers in etc/afpd/spotlight.c.

This area has history. CVE-2023-42464 was a type confusion in the same Spotlight RPC code, where the decoder trusted the type of a dictionary value. The fix added type checks on those values.

How we found them

We started with the decoder, since that’s where attacker bytes first arrive. It turned out to be well guarded, with length checks in the right places and the 2023 fix doing its job. We also looked at what the project already tests. Netatalk has a fuzzer, test/afpd/fuzz.c, but it drives AFP commands. It doesn’t reach the Spotlight decoder or the handlers behind it.

So we moved one step downstream, to the handlers that consume the decoded tree. A decoder can be correct and still produce output that its consumers don’t expect. For each handler we asked one question: what does this code assume about the tree that the decoder never promised? Two assumptions turned out to be wrong.

The array always has a first element. sl_rpc_fetchAttributeNamesForOIDArray, sl_rpc_storeAttributesForOIDArray and sl_rpc_fetchAttributesForOIDArray all fetch a CNID array from the request, check that the pointer isn’t NULL, and then read element zero:

cnids = dalloc_get(query, "DALLOC_CTX", 0, "sl_cnids_t", N);   /* NULL-checked */
memcpy(&uint64, cnids->ca_cnids->dd_talloc_array[0], sizeof(uint64_t));

Nothing checks how many elements there are. In the decoder, sl_unpack_CNID() handles a short CNID value (length 16 or less) as “empty” and adds nothing, which is safe. But if the outer length is greater than 16 and the inner count is zero, the copy loop runs zero times and the decoder still adds an sl_cnids_t whose array is empty. Its dd_talloc_array is NULL, and indexing [0] reads address zero.

Every element is a 64-bit integer. sl_rpc_openQuery() pulls kMDQueryItemArray out of the request as a generic array and passes it to sl_createCNIDArray(), which copies eight bytes from each element:

for (size_t i = 0; i < n; i++)
    memcpy(&cnids[i], p->dd_talloc_array[i], sizeof(uint64_t));   /* assumes uint64_t */

The marshalling format allows other element types in that array: a one-byte boolean, a four-byte nil, a short string. For any of those, the eight-byte copy reads past the end of the element’s heap allocation. The 2023 fix checks the type of the container, so an array is accepted as an array, but it never checks what’s inside.

Reproduction

We reproduced both bugs in a harness built from Netatalk’s own code, compiled with AddressSanitizer. We did not run them against a live afpd server. The maintainers’ regression tests cover that, as described in the fix section below.

For the NULL dereference, we used the project’s sl_pack() to build a valid request containing a one-element CNID array. We then changed the inner count from 1 to 0 while leaving the outer length above 16, and passed the result to the real sl_unpack_len(). The decoder accepted it and returned success. Running the handler’s access on the decoded tree produced:

AddressSanitizer: SEGV on unknown address 0x000000000000
  #0 ... in sl_rpc_fetchAttributeNamesForOIDArray  spotlight.c:1172

For the over-read, the harness ran the same copy loop over a real sl_array_t holding a one-byte sl_bool_t. AddressSanitizer reported an eight-byte read from a one-byte allocation. Without AddressSanitizer, the same program read seven bytes of whatever sat next to the boolean on the heap and printed no crash. So the read is real, but on its own it doesn’t land on unmapped memory.

What we corrected in our own report

We first sent both issues as one report, and two of its claims didn’t hold up when we re-tested.

We’d said the over-read also happened in dd_dump(), a debug routine that prints the tree. It doesn’t. dd_dump() checks each element’s type name and reads a boolean as a boolean, so our test cases never produced an eight-byte read there. We withdrew that claim.

We’d also scored the over-read as a confidentiality issue. Following the data showed otherwise: the eight bytes become an internal CNID filter for the search, and they’re never sent back to the client. A changed filter doesn’t leak the bytes that produced it. We removed the confidentiality impact.

We then split the report into two advisories, one per bug, each with the impact we could actually show. The maintainers’ published scores came out of that corrected version.

Impact

CVE-2026-110780 crashes the afpd process serving the attacker’s connection. In the default configuration Netatalk forks one child process per connection, so the crash ends the attacker’s own session and leaves other users connected. That’s why it’s a Medium and not more.

CVE-2026-110779 is a heap read past an allocation. We couldn’t show it disclosing data, and we couldn’t make it crash a normal build without first arranging the heap so the short element ends right at a page boundary. Neither bug gives a write primitive or code execution.

That still matters, for two reasons. First, both bugs sit in code that any authenticated user (or any guest, on servers that allow guests) can reach over the network. Second, they’re in the same Spotlight RPC code that produced CVE-2023-42464. Out-of-bounds reads in network-facing parsers are often the first half of an exploit chain, and they’re far cheaper to fix now than after someone pairs one with a write.

The fixes

Daniel Markstedt of the Netatalk team wrote both fixes on 28 July, the day after our report, and Andy Lemin reviewed them.

For CVE-2026-110780, commit 888bdaff8d makes the decoder reject a CNID value whose count is zero. We had suggested checking the length in each of the three handlers. Fixing it in the decoder is better, because it protects every consumer, including any added later.

For CVE-2026-110779, commit 7aba35c5f0 changes sl_createCNIDArray() to treat an empty array as an empty result. It also checks that each element is non-NULL, at least eight bytes, and tagged uint64_t before copying it. Any other element fails the request with a logged error.

Both commits add regression tests, including new cases in Netatalk’s AFP testsuite that send the malformed Spotlight requests over the wire to a running server.

What to do

Upgrade to Netatalk 4.5.2 or later; the current release is 4.6.1. If you build from a 4.5.1 source tree and can’t upgrade yet, Netatalk publishes standalone patches for CVE-2026-110779 and CVE-2026-110780.

If you can’t patch at all, turn Spotlight off in afp.conf. Your Mac users lose Finder search on network volumes until you upgrade.

[Global]
spotlight = no

If you run 4.5.0 or 4.5.1, check that setting even if you never turned Spotlight on, since those versions enable it by default. And if guest access is enabled anywhere, this is a good moment to confirm it’s still needed.

Disclosure timeline

  • 27 July 2026: reported privately to the Netatalk maintainers
  • 28 July 2026: fixes written
  • 6 September 2026: Netatalk 4.5.2 released with both fixes
  • 15 October 2026: advisories for CVE-2026-110779 and CVE-2026-110780 published on netatalk.io

Thanks to Daniel Markstedt for the fixes and the regression tests, and to Andy Lemin for reviewing them. Fixes the next day, with network-level tests included, is a very good response.

References

If you ship a network service with its own binary protocol and want someone to look at what its handlers assume, that’s part of our penetration testing work.

Know what an attacker would reach first.

Tell us what you run and what worries you. We will come back with a scope, a fixed price and the earliest week we can start.