Mobile Development 11 min read

How to Diagnose and Mitigate nano_free Crashes on iOS 10 – A Deep Dive

This article analyzes the nano_free crash issue that appeared on iOS 10.0‑10.1.1, explores several attempted fixes such as dylib replacement, source compilation, environment variable tweaks, and hook strategies, and finally proposes a guarded zone solution to reduce crash probability.

WeChat Client Technology Team
WeChat Client Technology Team
WeChat Client Technology Team
How to Diagnose and Mitigate nano_free Crashes on iOS 10 – A Deep Dive

Background

On iOS 10.0‑10.1.1 a new class of crashes with a stack trace containing nano_free started appearing, affecting many users despite no new app version being released. The crash stack resembles a typical wild‑pointer issue, but after iOS 10’s release it rose to the top of WeChat’s crash rankings.

Discussions with internal and external teams indicated this was a common problem, likely an Apple bug. A bug report was filed and Apple replied that iOS 10.2 Beta includes stability improvements.

After iOS 10.2 Beta was released, the crash disappeared on that version and also on iOS 9, confirming the issue is version‑specific.

Apple’s suggested fix is to encourage users to upgrade the system, which is a long‑term solution. However, the crash rate remains high in our development branch, so we investigated deeper.

Attempts

Attempt 1: Replace dylib

The relevant libsystem_malloc.dylib can be found under ~/Library/Developer/Xcode/iOS DeviceSupport/. We tried using the iOS 9.3.5 version, but linking failed with an error indicating the dylib belongs to the System.framework umbrella framework, which the linker rejects.

Attempt 2: Compile from source

The source for libsystem_malloc.dylib is available at Apple’s open‑source site . The iOS 9.3.5 source (libmalloc‑67.40.1.tar.gz) is incomplete and cannot be compiled.

Reading the source

Inside libsystem_malloc.dylib there are two memory‑management implementations: nano zone and scalable zone. Nano zone handles small allocations (0‑256 bytes) and overlaps with scalable zone, acting as an optimization for tiny allocations.

Both zones are selected via the MallocZoneNano environment variable:

When MallocZoneNano=1, the default zone is nano zone; allocations that do not fit fall through to a helper (scalable) zone.

When MallocZoneNano=0, the default zone is scalable zone.

On iOS 9 and iOS 10.2 Beta, MallocZoneNano=0; on other iOS versions it is 1. Apple’s fix merely disables the nano zone rather than solving the underlying bug.

Attempt 3: Modify environment variable

Using setenv to set MallocZoneNano=0 had no effect because the dylib initializes before the app starts.

Adding LSEnvironment in Info.plist works only on macOS.

Setting the variable in Xcode’s scheme stopped the crash locally, but this only applies to debugging builds.

Thus, disabling the nano zone works in controlled environments but cannot be shipped in production.

Attempt 4: Hook

Since we cannot fully disable the nano zone, we tried to bypass it. By creating a custom zone (guard zone) with malloc_zone_create and using fishhook to redirect common allocation functions (e.g., malloc, malloc_zone_malloc) to the guard zone, crash frequency decreased but was not eliminated because system libraries still call the nano zone.

Attempt 5: Skip nano zone

Nano zone manages allocations up to 256 bytes. By altering the nano zone’s function pointers to artificially inflate the requested size, allocations are forced into the helper (scalable) zone and later corrected. However, this made crashes almost certain, suggesting the crash occurs when memory allocated in the scalable zone is mistakenly freed in the nano zone.

Reproducing the issue

We examined how the runtime determines whether a pointer belongs to the nano zone. On ARM, the check is (ptr >> 28) == 0x17. Tests showed that pointers larger than 256 bytes eventually also fall into the 0x17 segment, indicating a misclassification.

By repeatedly allocating 257‑byte blocks and monitoring their segment, we observed the misclassification and reproduced the crash in a minimal Single View Application.

Solution

To reliably avoid nano_free crashes, the most practical approach is to create a custom guard zone and redirect nano zone function pointers to it. The redirection logic works as follows:

Create a guard zone with malloc_zone_create.

Replace nano zone function pointers:

Because some pointers may already be allocated in the nano zone before redirection, each incoming pointer must be examined to locate its current zone before handling.

This method is still in a gray‑release stage and should be used with appropriate rollout and fallback strategies.

iOSmemory managementmallocCrashnano_free
WeChat Client Technology Team
Written by

WeChat Client Technology Team

Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.