Skip to content

[iOS] Image.compress (manual) crashes the app in Release builds (vImageScale_ARGB8888 use-after-free) and on unreadable images #430

Open

Description

@ArjunPatidar-dev

Title: [iOS] Image.compress (manual) crashes the app in Release builds (vImageScale_ARGB8888 use-after-free) and on unreadable images

Current behavior

Image.compress(uri, { compressionMethod: 'manual', maxWidth, maxHeight, quality }) crashes the whole app on iOS in two cases. Both are in ios/Image/ImageCompressor.swift and are unchanged in 2.0.4.

1. Release builds crash while resizing (EXC_BAD_ACCESS)

EXC_BAD_ACCESS
vVertical_Scale_ARGB_8888_Accelerate
vImageScale_ARGB8888
ImageCompressor.manualResize(_:maxWidth:maxHeight:)

manualResize passes &sourceData / &targetData (Swift [UInt8] arrays) into CGContext(data:) and vImage_Buffer(data:). A pointer from &array is only valid for the duration of the call that receives it. With -O, Swift can release sourceData before vImageScale_ARGB8888 reads it, so vImage reads freed memory. Debug builds keep the array alive longer, which is why this only shows up in App Store / TestFlight builds.

Related issues in the same function:

  • free(&targetData) is called on Swift-managed memory (undefined behaviour).
  • targetRef! is force-unwrapped.
  • The source size is taken from image.size (points) instead of cgImage.width / cgImage.height (pixels).

2. A missing or unreadable image terminates the app instead of rejecting

When loadImage returns nil, manualCompressHandler (and autoCompressHandler) call NSException(...).raise(). Swift's do/catch in ImageMain.image_compress can't catch an Objective-C exception, so the app aborts. writeImage and copyExifInfo also crash on failure through force unwraps:

  • jpegData(...)! and pngData()!
  • image.cgImage!
  • CGImageSourceCreateWithURL(...)!
  • CGImageDestinationCreateWithData(...)!

3. (Minor) Some images come out stretched and sideways

scaleAndRotateImage creates its CGContext with the source's bitsPerComponent and bitmapInfo. For 16-bit PNG, PNG with alpha and 10-bit HEIC, that context fails to create, so the function returns the unrotated image. manualResize then scales it to the rotated dimensions, which stretches it.

Expected behavior

  • Resizing works in Release builds without memory errors.
  • A missing, corrupt or unsupported image rejects the promise with an error instead of crashing the app.
  • Images with an EXIF orientation come out upright and correctly proportioned, whatever their pixel format.

Suggested fixes (we have these working in a local patch and can open a PR):

  • Resize with vImage-owned buffers (vImage_Buffer(cgImage:) → vImageScale_ARGB8888 → vImageCreateCGImageFromBuffer). This also lowers peak memory for a 12 MP photo from about 150 MB to about 60 MB.
  • Make the handlers throws and replace raise() and ! with Swift errors, so that image_compress calls reject(...).
  • In scaleAndRotateImage, retry with an 8-bit sRGB context when the source-format context can't be created.

Platform

  • Android
  • iOS

React Native Version

0.87.1 (New Architecture, react-native-nitro-modules 0.37.1)

React Native Compressor Version

2.0.3 (the iOS image code is unchanged in 2.0.4)

Reproducible Steps And Demo

Crash 1: resize in a Release build

  1. Build the app in the Release configuration (TestFlight / App Store, or Release on a device or simulator).
  2. Take or pick a 12 MP or 48 MP photo with the camera.
  3. Call:
    await Image.compress(photoUri, {
      compressionMethod: 'manual',
      maxWidth: 1080,
      maxHeight: 1920,
      quality: 0.9,
    });
  4. The app crashes intermittently with EXC_BAD_ACCESS in vVertical_Scale_ARGB_8888_Accelerate. We saw it in production on an iPhone 11 running iOS 27.0.1. The same code never crashes in Debug.

You can also reproduce it outside React Native: compile manualResize on its own with swiftc -O, resize a 12 MP image, and it crashes at the same frame. With -Onone, it doesn't.

Crash 2: missing or unreadable image

  1. Any build configuration.
  2. Call:
    await Image.compress('file:///path/that/does/not/exist.jpg', {
      compressionMethod: 'manual',
    });
    You can also pass a truncated JPEG instead.
  3. The app aborts with an uncaught NSException (unsupported_value) instead of the promise rejecting.

Issue 3: stretched, sideways image

  1. Create a 16-bit PNG, or a PNG with alpha, tagged with EXIF Orientation 6.
  2. Compress it with the options from crash 1.
  3. The output is stretched and rotated 90°.

Activity

  1. github-actions commented on Oct 26, 2026

    @github-actions

    👋 @ArjunPatidar-dev
    Thanks for opening your issue here! If you find this package useful hit the star🌟!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions