Skip to content

fix: 16 KB page-size RELRO compatibility #1047

Description

@ovitrif

Context

Summary

Android 17 shows this compatibility warning when launching the dev build on Pixel 10 Pro:

This app isn't 16 KB-compatible. RELRO alignment check failed.

The APK is already built with 16 KB PT_LOAD alignment and passes APK zip alignment. The remaining failure is PT_GNU_RELRO alignment in packaged native libraries from external and Synonym artifacts.

Repro Context

adb shell getconf PAGE_SIZE
# 4096

The warning appeared on a 4 KB page-size device, so the app still runs there. The APK must still pass the stricter Android 17 compatibility checks before release.

APK inspected:

app/build/outputs/apk/dev/debug/bitkit-dev-debug-182-universal.apk

Baseline checks that already passed:

arm64-v8a PT_LOAD alignment: >= 0x4000
zipalign -v -c -P 16 4 bitkit-dev-debug-182-universal.apk: Verification successful

Failing Libraries

libandroidx.graphics.path.so       RELRO end=0x6000    source: androidx.graphics:graphics-path:1.0.1
libdatastore_shared_counter.so     RELRO end=0xd000    source: androidx.datastore:datastore-core-android:1.2.0
libpaykit.so                       RELRO end=0x859000  source: com.synonym:paykit-android:0.1.0-rc8
libsurface_util_jni.so             RELRO end=0x5000    source: androidx.camera:camera-core:1.5.2

Checked native libraries that passed in this APK:

libbitkitcore.so
libldk_node.so
libvss_rust_client_ffi.so
libjnidispatch.so
libbarhopper_v3.so
libimage_processing_util_jni.so

Requirements

External Dependencies

  • Bump androidx.datastore:datastore-preferences from 1.2.0 to 1.2.1.
  • Run compile, unit tests, and app launch checks after the DataStore bump.
  • Verify libdatastore_shared_counter.so passes the RELRO check after the bump.
  • Find a released androidx.graphics:graphics-path version where libandroidx.graphics.path.so passes the RELRO check.
  • Bump/migrate Graphics Path to that version and update Bitkit code for any API changes.
  • If no fixed Graphics Path version exists, file/link the upstream AndroidX issue and keep this item open until a fixed release is available or Bitkit replaces the dependency path with equivalent behavior.
  • Find a released CameraX version where libsurface_util_jni.so passes the RELRO check.
  • Bump/migrate CameraX to that version and update Bitkit code for any API changes.
  • If no fixed CameraX version exists, file/link the upstream AndroidX issue and keep this item open until a fixed release is available or Bitkit replaces the dependency path with equivalent scanner behavior.
  • Regression test DataStore-backed settings/session state across app restarts.
  • Regression test QR scanner/camera initialization and scanning.
  • Regression test UI surfaces that rely on AndroidX graphics/path rendering.

Synonym Artifacts

  • Rebuild com.synonym:paykit-android from source with the current Android native build pipeline and NDK r28 or newer.
  • Add the 16 KB compatibility check to the Paykit Android release process.
  • Publish a new Paykit Android artifact after libpaykit.so passes the check.
  • Bump Bitkit from com.synonym:paykit-android:0.1.0-rc8 to the fixed Paykit release.
  • Verify libpaykit.so passes the RELRO check in the rebuilt Bitkit APK/AAB.
  • Keep the same check in place for future Bitkit consumption of bitkit-core, ldk-node, vss-client, and other native artifacts.

Bitkit Validation

  • Add scripts/check-16kb-compat.sh or equivalent.
  • The script must check APK and AAB outputs for:
    • PT_LOAD alignment below 0x4000 on arm64-v8a and x86_64 libraries.
    • PT_GNU_RELRO end addresses not aligned to 0x4000 on arm64-v8a and x86_64 libraries.
    • APK zip alignment via zipalign -v -c -P 16 4.
  • Add the script to release validation and CI artifact checks.
  • Rebuild the affected APK/AAB artifacts.
  • Confirm the rebuilt artifacts report zero failing native libraries.

Acceptance Criteria

  • scripts/check-16kb-compat.sh passes for the dev debug universal APK.
  • scripts/check-16kb-compat.sh passes for release APK/AAB artifacts used for distribution.
  • The failing libraries listed above either pass the check or are no longer packaged.
  • A fresh install on Android 17 launches without the 16 KB compatibility dialog.
  • A true 16 KB page-size environment launches the app successfully.

Verification Commands

Use artifact checks as the source of truth. The Android dialog can be suppressed per install/device state.

APK=app/build/outputs/apk/dev/debug/bitkit-dev-debug-*-universal.apk
NDK_READELF="$ANDROID_HOME/ndk/28.1.13356709/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-readelf"
ZIPALIGN="$ANDROID_HOME/build-tools/35.0.0/zipalign"

"$ZIPALIGN" -v -c -P 16 4 $APK

rm -rf /tmp/bitkit-16kb-check
mkdir -p /tmp/bitkit-16kb-check
unzip -q $APK 'lib/arm64-v8a/*.so' 'lib/x86_64/*.so' -d /tmp/bitkit-16kb-check

python3 - <<'PY'
import pathlib
import subprocess
import sys

readelf = pathlib.Path.home() / 'Library/Android/sdk/ndk/28.1.13356709/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-readelf'
root = pathlib.Path('/tmp/bitkit-16kb-check/lib')
failed = False

for so in sorted(root.glob('*/*.so')):
    out = subprocess.check_output([str(readelf), '-l', str(so)], text=True)
    for line in out.splitlines():
        parts = line.split()
        if not parts:
            continue
        if parts[0] == 'LOAD':
            align = int(parts[-1], 16)
            if align < 0x4000:
                print(f'FAIL LOAD {so}: align=0x{align:x}')
                failed = True
        if parts[0] == 'GNU_RELRO':
            vaddr = int(parts[2], 16)
            memsz = int(parts[5], 16)
            end = vaddr + memsz
            if end % 0x4000 != 0:
                print(f'FAIL RELRO {so}: end=0x{end:x}')
                failed = True

if failed:
    sys.exit(1)
print('16 KB native compatibility checks passed')
PY

Fresh-install runtime check:

adb uninstall to.bitkit.dev || true
adb install app/build/outputs/apk/dev/debug/bitkit-dev-debug-*-universal.apk
adb shell monkey -p to.bitkit.dev -c android.intent.category.LAUNCHER 1

16 KB environment check:

adb shell getconf PAGE_SIZE
# expected: 16384

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Task.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions