Skip to content

Instantly share code, notes, and snippets.

@pioz
Created June 30, 2026 07:15
Show Gist options
  • Select an option

  • Save pioz/28b4095c9e72b7822c0dfb805d344256 to your computer and use it in GitHub Desktop.

Select an option

Save pioz/28b4095c9e72b7822c0dfb805d344256 to your computer and use it in GitHub Desktop.

Fix: zbar 0.23.93 SIGSEGV in zbar_scan_image on macOS (Apple Silicon)

Recent Homebrew builds of zbar 0.23.93 crash with SIGSEGV inside zbar_scan_image() on Apple Silicon Macs (observed on macOS 26 "Tahoe", arm64, with the current Apple clang). This breaks everything that links libzbar: zbarimg, Python pyzbar, the Ruby qrscan gem, etc.

This guide shows how to install a patched zbar through Homebrew, from scratch, on a clean machine. It is a temporary workaround until the upstream fix is released — see Upstream below.


Symptom

Any use of zbar segfaults, even on a trivial image:

$ zbarimg some_qr.png
[1]    47701 segmentation fault  zbarimg some_qr.png
$ echo $?
139

From Ruby (qrscan gem) you'll see a [BUG] Segmentation fault with a backtrace ending in CFUNC :scan. Typically it "worked until a few days ago" — what changed is the compiler/OS, not your code.

Root cause

The image scan loop in zbar/img_scanner.c advances the row pointer through the movedelta macro:

#define movedelta(dx, dy)                \
    do {                                 \
        x += (dx);                       \
        y += (dy);                       \
        p += (dx) + ((uintptr_t)(dy)*w); \
    } while (0);

The reverse passes call movedelta(-1, …), i.e. with a negative dx/dy. Because the delta is computed in uintptr_t, the negative int becomes a huge unsigned value and the pointer is advanced relying on two's-complement wraparound. Pointer arithmetic that overflows the object is undefined behavior. It worked for years, but recent clang exploits the UB at -O1 and above: the scan pointer is folded to (void *)-1 and the next *p dereference crashes.

Proof: the same source built at -O0 does not crash; -O1/-Os/-O2 do. The fix is to compute the delta in signed ptrdiff_t arithmetic.


The fix

Install a patched zbar into its own local tap, built from source. The patch is downloaded straight from the upstream PR, so there is nothing fragile to paste.

# 1. Create a local tap to hold the patched formula
brew tap-new local/patches --no-git

# 2. Write the patched formula
cat > "$(brew --repository)/Library/Taps/local/homebrew-patches/Formula/zbar.rb" <<'RUBY'
class Zbar < Formula
  desc "Suite of barcodes-reading tools"
  homepage "https://linuxtv.org/downloads/zbar/"
  url "https://linuxtv.org/downloads/zbar/zbar-0.23.93.tar.bz2"
  sha256 "83be8f85fc7c288fd91f98d52fc55db7eedbddcf10a83d9221d7034636683fa0"
  license "LGPL-2.1-only"
  revision 4

  depends_on "pkgconf" => :build
  depends_on "xmlto" => :build

  depends_on "imagemagick"
  depends_on "jpeg-turbo"

  on_macos do
    depends_on "fontconfig"
    depends_on "freetype"
    depends_on "gettext"
    depends_on "glib"
    depends_on "liblqr"
    depends_on "libomp"
    depends_on "libtool"
    depends_on "little-cms2"
  end

  on_linux do
    depends_on "dbus"
  end

  # Fix pointer-arithmetic UB in the scan loop that recent clang miscompiles
  # at -O1+, causing SIGSEGV in zbar_scan_image() on arm64 macOS.
  # Upstream: https://github.com/mchehab/zbar/pull/332
  patch do
    url "https://github.com/mchehab/zbar/commit/3eb0cb195408212fd54e6a1e1e9b9b3c0ec868e7.patch?full_index=1"
    sha256 "1d9c8f8cdcec264df24076119c3f69c5e473cbd76a1d33ff241b39c7b8dd6f5a"
  end

  def install
    ENV["XML_CATALOG_FILES"] = etc/"xml/catalog"
    ENV.append "LDFLAGS", "-lintl" if OS.mac?

    system "./configure", "--disable-silent-rules",
                          "--disable-video",
                          "--without-python",
                          "--without-qt",
                          "--without-gtk",
                          "--without-x",
                          *std_configure_args
    system "make", "install"
  end

  test do
    system bin/"zbarimg", "-h"
  end
end
RUBY

# 3. Build & install it from source (the bottle is the broken one, so force source)
HOMEBREW_NO_INSTALL_FROM_API=1 brew install --build-from-source local/patches/zbar

If zbar is already installed (e.g. pulled in as a dependency), use reinstall instead of install in step 3:

HOMEBREW_NO_INSTALL_FROM_API=1 brew reinstall --build-from-source local/patches/zbar

Verify

zbarimg /path/to/any_qr.png   # decodes, exits 0 — no segfault

A blank image is enough to prove the crash is gone (the original bug fires before any decoding happens).

Rebuild native bindings (if needed)

If a language binding was compiled against the broken libzbar, rebuild it so it links the patched one. For the Ruby qrscan gem:

gem pristine qrscan      # or: bundle pristine qrscan

Caveats & maintenance

  • brew upgrade or a plain brew reinstall zbar will pull the broken core bottle again and reintroduce the crash. Just re-run the reinstall command above to restore the patched build.
  • This pins zbar to 0.23.93 with the patch applied. That is intentional.
  • The revision 4 number is arbitrary; it only needs to differ from whatever core ships so Homebrew rebuilds the keg.

Reverting

brew untap local/patches
brew reinstall zbar

Upstream

The real fix is a one-liner submitted upstream:

Once it is merged and a new zbar release lands in Homebrew, drop this workaround (brew untap local/patches && brew reinstall zbar).

Appendix — the patch

For reference, the entire fix is:

--- a/zbar/img_scanner.c
+++ b/zbar/img_scanner.c
@@ -858,11 +858,11 @@
 }
 #endif
 
-#define movedelta(dx, dy)                \
-    do {                                 \
-	x += (dx);                       \
-	y += (dy);                       \
-	p += (dx) + ((uintptr_t)(dy)*w); \
+#define movedelta(dx, dy)                                        \
+    do {                                                         \
+	x += (dx);                                               \
+	y += (dy);                                               \
+	p += (ptrdiff_t)(dx) + (ptrdiff_t)(dy) * (ptrdiff_t)(w); \
     } while (0);
 
 static void *_zbar_scan_image(zbar_image_scanner_t *iscn, zbar_image_t *img)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment