From fc65b8df87091a238f4a0ba4b4dd0f4c8964f32f Mon Sep 17 00:00:00 2001 From: GenericJam Date: Thu, 22 Oct 2026 20:36:35 -0600 Subject: [PATCH 1/5] Device runtime on OTP 29.1.2 + OpenSSL 3.5.9 (otp-d199a49a); stop shadowing the runtime's ssl with the host's (MOB-493, MOB-494) - @otp_hash 5c9c69fc -> d199a49a; bundled_versions.exs records the bundle and its OTP tag; android-x86_64 caches are fingerprinted too. - Release scripts rebuild from a clean OTP tag: vendored xcomp confs for all five targets, patch 0003 (iOS {spawn, LinkedInDriver}), the 0002 marker that upstream already matched, static asn1rt_nif.a on Android, the xmerl deps regeneration on iOS, OpenSSL 3.5.9. - Deploys and Android releases no longer push the host's ssl into the app's beams dir (it came first on -pa and shadowed lib/ssl-*), and delete what an older mob_dev left there. - Requires mob ~> 0.9.25 (finds erts-17.1.1 at runtime); erlang 29.1.2 pin; doctor text; decision record. --- .tool-versions | 2 +- CHANGELOG.md | 66 +++- build_release.md | 47 ++- ...-22-otp-29.1.2-runtime-no-host-otp-apps.md | 73 ++++ lib/mix/tasks/mob.doctor.ex | 2 +- lib/mob_dev/deployer.ex | 65 +++- lib/mob_dev/native_build.ex | 214 ++---------- lib/mob_dev/otp_downloader.ex | 2 +- lib/mob_dev/release.ex | 22 +- lib/mob_dev/release_android.ex | 8 +- .../bundled_runtime/fingerprint.ex | 8 +- mix.exs | 7 +- priv/security/bundled_versions.exs | 26 +- scripts/release/openssl/README.md | 13 +- .../openssl/_build_otp_android_arm64.sh | 15 + .../openssl/_build_otp_android_x86_64.sh | 13 +- .../0003-ios-spawn-linked-in-driver.patch | 45 +++ scripts/release/tarball_android_arm32.sh | 12 +- .../release/xcomp/erl-xcomp-arm-android.conf | 316 ++++++++++++++++++ .../xcomp/erl-xcomp-arm64-android.conf | 305 +++++++++++++++++ .../release/xcomp/erl-xcomp-arm64-ios.conf | 275 +++++++++++++++ .../xcomp/erl-xcomp-arm64-iossimulator.conf | 275 +++++++++++++++ scripts/release/xcompile_android_arm32.sh | 11 + scripts/release/xcompile_ios_device.sh | 19 +- scripts/release/xcompile_ios_sim.sh | 23 ++ test/mob_dev/deployer_test.exs | 22 ++ .../bundled_runtime/fingerprint_test.exs | 7 +- 27 files changed, 1627 insertions(+), 266 deletions(-) create mode 100644 decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md create mode 100644 scripts/release/patches/0003-ios-spawn-linked-in-driver.patch create mode 100644 scripts/release/xcomp/erl-xcomp-arm-android.conf create mode 100644 scripts/release/xcomp/erl-xcomp-arm64-android.conf create mode 100644 scripts/release/xcomp/erl-xcomp-arm64-ios.conf create mode 100644 scripts/release/xcomp/erl-xcomp-arm64-iossimulator.conf diff --git a/.tool-versions b/.tool-versions index b4ba75b8..b3d9d31b 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ elixir 1.20.0-otp-29 -erlang 29.0 +erlang 29.1.2 zig 0.17.0-dev.269+ebff43698 diff --git a/CHANGELOG.md b/CHANGELOG.md index d4df06c4..f792aab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,37 @@ ### Security +- **Device runtime rebuilt on OTP 29.1.2 and OpenSSL 3.5.9 (`@otp_hash` + `5c9c69fc` → `d199a49a`; MOB-493, MOB-494).** Every app shipped OTP 29.0 + (erts 17.0, ssl 11.7, public_key 1.21), which lacks 47 OTP security fixes. + Among them: CVE-2026-89422 and CVE-2026-55953 (critical; TLS clients that + skip or bypass server authentication), CVE-2026-42789 (public_key accepts a + non-CA as an intermediate issuer), CVE-2026-94200 (TLS client ticket-store + poisoning) and CVE-2026-75538 (inet TCP driver overflow from an + unauthenticated peer). The crypto NIF was statically linked against + OpenSSL 3.4.0, which had 55 CVEs fixed in later 3.4.x releases, and the 3.4 + branch reached end of life on 2026-10-22. The new runtime is erts 17.1.1, + ssl 11.7.8, public_key 1.21.8 and inets 9.8.1, with OpenSSL 3.5.9 (LTS + until 2030). Elixir 1.20.1 and the bundled exqlite BEAMs are unchanged. + `bundled_versions.exs` records the new bundle, including its OTP tag. +- **Apps run the runtime's ssl, not the developer's (MOB-493).** `mix + mob.deploy` (Android and the iOS simulator) and `mix mob.release --android` + copied the *host's* OTP `ssl` app into the app's own BEAM directory. That + directory comes first on the code path, so it shadowed the runtime's + `lib/ssl-*`: every Android release and dev install ran whatever ssl the + building machine had. With a 29.0 host that is 11.7, which has + CVE-2026-89422 and CVE-2026-55953, whatever runtime the app shipped. The + copy dates from runtimes without ssl; every current runtime ships it. + Deploys now also delete those files from a device that an older mob_dev + left them on. Verified on a Moto G: before, `code:which(ssl)` was + `files/otp//ssl.beam` (11.7); after, `lib/ssl-11.7.8`. On iOS, the + physical-device dev build no longer compiles a no-op `ssl` shim for + LiveView apps (on a phone their TLS failed with `ssl_not_supported`, and + `mix mob.release --ios` could pick the shim up). That build and the iOS + release no longer copy the host's runtime_tools, asn1 and public_key over + the runtime's. The fallback EPMD patch now checks the `run_daemon(g)` call + site rather than any `#ifndef NO_DAEMON`, which upstream `epmd.c` already + has. - **Dev server: bandit 1.12.5 → 1.13.1 (MOB-482).** Fixes EEF-CVE-2026-105659 (a client trickling HTTP/2 `WINDOW_UPDATE` frames could pin stream processes indefinitely) and EEF-CVE-2026-105660 (an HTTP/1 absolute-form request @@ -41,6 +72,23 @@ a failed boot on an app generated before mob_new added them shows a blank screen. Detected, not patched: `MobBridge.kt` is app-owned. +### Upgrading + +- **Needs mob ≥ 0.9.25.** The new runtime's ERTS directory is `erts-17.1.1`. + Older mob hard-codes `erts-17.0` in the Android launcher and in the + iOS-simulator build, so an app on mob ≤ 0.9.24 crashes at boot with this + runtime. Physical-iOS and release builds already read the version from the + tarball. Update both: `mix deps.update mob mob_dev`, then + `mix mob.deploy --native`. +- Host `.tool-versions` moves to erlang 29.1.2 (mob_dev, mob, mob_new and + generated apps). +- **MOB-495.** `mix mob.security_scan --strict` and `mix mob.release + --security-gate` now fail on a `5c9c69fc` runtime (critical ssl advisories + CVE-2026-89422 and CVE-2026-55953, OpenSSL 3.4 end of life). The + `d199a49a` runtime in this release passes them; delete the old + `~/.mob/cache/otp-*-5c9c69fc` dirs once no app on this machine still uses + that runtime. + ### Changed - **`mix mob.security_scan` reports CVEs in the bundled OTP and OpenSSL @@ -61,7 +109,23 @@ passes, instead of only "security scan clean". - `mix mob.doctor`'s JDK rows no longer name AGP 8.2.0, which new apps no longer use. - +- **The release scripts rebuild the runtime from a clean OTP tag.** The + Android builds and both iOS builds now install vendored xcomp confs from + `scripts/release/xcomp/`, as x86_64 already did: + - arm64 and arm32 Android: `llvm-ar`/`llvm-ranlib` (upstream's stubs link + an empty `libei.a` on macOS), 16 KB page alignment, `-Os` with + gc-sections, and no debugger/observer/et/cdv. arm32 also passes + `--disable-year2038`, because 32-bit bionic's `time_t` can't widen. + - iOS: `DED_LD` goes through the cc driver, because OTP 29.1's hardening + flags break raw `ld`. + + The Android scripts also build the static `asn1rt_nif.a`, which `boot` + only emits with `--enable-static-nifs`. The iOS `{spawn, LinkedInDriver}` + erts fix, which was hand-applied to earlier builds, is now + `scripts/release/patches/0003-ios-spawn-linked-in-driver.patch`, applied by + both iOS xcompile scripts. The device script's check for patch 0002 (EPMD + `NO_DAEMON`) looked for `#ifndef NO_DAEMON`, which upstream `epmd.c` already + contains, so it skipped the patch; it now looks for the patch's own comment. - **`mix mob.security_scan` reports CVEs in the project's SQLite.** SQLite is compiled per app from `deps/exqlite/c_src/sqlite3.c`, and the `bundled_runtime` layer only printed its version beside a link to diff --git a/build_release.md b/build_release.md index fc3dc38d..0583e5cc 100644 --- a/build_release.md +++ b/build_release.md @@ -55,31 +55,16 @@ Built from a full cross-compiled OTP release for `arm-unknown-linux-androideabi` (armeabi-v7a). Same structure as arm64. Required for 32-bit-only devices (e.g. Motorola E 2020). -`asn1rt_nif.a` must be compiled separately — it is not emitted by the OTP build -system for arm32. Build it with: +`asn1rt_nif.a`: OTP only emits the static asn1 NIF when configured with +`--enable-static-nifs`, which the Android builds don't use, so the +Android scripts build it with OTP's own target after `boot`: ```bash -NDK=~/Library/Android/sdk/ndk/27.2.12479018/toolchains/llvm/prebuilt/darwin-x86_64/bin -OTP_SRC=~/code/otp - -$NDK/armv7a-linux-androideabi21-clang \ - -march=armv7-a -mfloat-abi=softfp -mthumb \ - -fvisibility=hidden -fno-common -fno-strict-aliasing \ - -fstack-protector-strong -O2 \ - -I "$OTP_SRC/erts/arm-unknown-linux-androideabi" \ - -I "$OTP_SRC/erts/include/arm-unknown-linux-androideabi" \ - -I "$OTP_SRC/erts/emulator/beam" \ - -I "$OTP_SRC/erts/include" \ - -DHAVE_CONFIG_H \ - -DSTATIC_ERLANG_NIF_LIBNAME=asn1rt_nif \ - -c "$OTP_SRC/lib/asn1/c_src/asn1_erl_nif.c" \ - -o /tmp/asn1rt_nif_arm32.o - -$NDK/llvm-ar rc /tmp/asn1rt_nif_arm32.a /tmp/asn1rt_nif_arm32.o -$NDK/llvm-ranlib /tmp/asn1rt_nif_arm32.a +ERL_TOP=~/code/otp make -C ~/code/otp/lib/asn1/c_src TARGET=arm-unknown-linux-androideabi static_lib ``` -Then include it in the tarball at `erts-/lib/asn1rt_nif.a` (see Step 2b). +(`xcompile_android_arm32.sh` does this; arm64 and x86_64 likewise.) The +tarball script picks it up from `lib/asn1/priv/lib//asn1rt_nif.a`. ### iOS device (`otp-ios-device-.tar.gz`) @@ -127,8 +112,17 @@ Built from a cross-compiled OTP for `aarch64-apple-iossimulator`. Needs: ## Prerequisites -- A cross-compiled OTP build. The OTP source tree at `~/code/otp` (commit `7721ab74`) - has iOS simulator and Android targets already compiled. +- The OTP source at `~/code/otp`, checked out at an **upstream release tag** + (`otp-d199a49a` was built from `OTP-29.1.2`): + `git clone --depth 1 --branch OTP-29.1.2 https://github.com/erlang/otp.git ~/code/otp`. + Take the newest patch release of the current major; the security fixes only + land there (29.0.x stopped getting them once 29.1 shipped). +- OpenSSL at `~/code/openssl` — see [`scripts/release/openssl/README.md`](scripts/release/openssl/README.md) + for the pinned tag. Build all targets with the `openssl/*.sh` scripts first. +- The mob patches in [`scripts/release/patches/`](scripts/release/patches/) are applied by the + iOS xcompile scripts (0001/0002 device-only fork guards, 0003 the + `{spawn, LinkedInDriver}` fix for both iOS targets). Each one must still apply + to the new tag; if a patch fails, port it before building. - `gh` CLI authenticated to the `GenericJam` GitHub account. --- @@ -137,10 +131,11 @@ Built from a cross-compiled OTP for `aarch64-apple-iossimulator`. Needs: ```bash cd ~/code/otp -git rev-parse --short HEAD # e.g. 7721ab74 +git rev-parse --short=8 HEAD # e.g. d199a49a for OTP-29.1.2 ``` -Use this hash everywhere below as ``. +Use this hash everywhere below as ``, and record the tag in the +`bundled_versions.exs` entry (`otp_version`) so the source is recoverable. --- @@ -226,7 +221,7 @@ ERTS_LIB32="$STAGE32/erts-16.3/lib" cp "$OTP_SRC/erts/emulator/zstd/obj/arm-unknown-linux-androideabi/opt/libzstd.a" "$ERTS_LIB32/" cp "$OTP_SRC/erts/emulator/pcre/obj/arm-unknown-linux-androideabi/opt/libepcre.a" "$ERTS_LIB32/" cp "$OTP_SRC/erts/emulator/ryu/obj/arm-unknown-linux-androideabi/opt/libryu.a" "$ERTS_LIB32/" -cp /tmp/asn1rt_nif_arm32.a "$ERTS_LIB32/asn1rt_nif.a" +cp "$OTP_SRC/lib/asn1/priv/lib/arm-unknown-linux-androideabi/asn1rt_nif.a" "$ERTS_LIB32/asn1rt_nif.a" ERTS_INC32="$STAGE32/erts-16.3/include" mkdir -p "$ERTS_INC32" diff --git a/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md b/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md new file mode 100644 index 00000000..377b670d --- /dev/null +++ b/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md @@ -0,0 +1,73 @@ +# Runtime rebuilt on OTP 29.1.2 / OpenSSL 3.5.9; no host OTP apps on the device + +- Date: 2026-10-22 +- Status: accepted +- Issues: MOB-493, MOB-494 + +## Context + +The `otp-5c9c69fc` runtime was OTP 29.0 with OpenSSL 3.4.0. It lacked 47 OTP +security fixes (two of them critical TLS client bugs), and OpenSSL 3.4.0 +lacked 55 CVEs, with the 3.4 branch going end-of-life on 2026-10-22. The +29.0.x line no longer gets patches, so the fix is the newest 29.x (29.1.2) +with OpenSSL on the 3.5 LTS branch. + +While verifying the new runtime on a phone, `code:which(ssl)` turned out to +point at `files/otp//ssl.beam` instead of the runtime's +`lib/ssl-11.7.8`. Three places copied the **host's** ssl app into the app's +flat BEAM directory: `Deployer.collect_beam_dirs/0`, +`ReleaseAndroid.collect_beam_dirs/0` and the iOS-sim native build. That +directory comes first on `-pa`. The copy was added when the runtimes had no +ssl; every runtime since the crypto-enabled tarballs ships one. So every +Android release and dev install ran the building machine's ssl, and a runtime +upgrade alone fixed nothing in TLS. Review found two iOS siblings: + +- The physical-iOS dev build compiled a no-op `ssl` shim into the beams dir + for LiveView apps, so on a phone their ssl returned `ssl_not_supported`. + `mix mob.release --ios` reuses that dir. +- That build and the iOS release script also copied the host's + runtime_tools, asn1 and public_key into `lib/`. The code server + takes the highest version, so a newer host could replace the runtime's + patched public_key. + +## Decision + +1. Publish a **new** hash, `otp-d199a49a` (the commit of the OTP-29.1.2 tag), + rather than clobbering `5c9c69fc`, as in + 2026-06-17-otp-hash-elixir-1.20.1.md. erts moves to 17.1.1. +2. Rebuild from a pristine upstream tag, with every build input in this repo: + - vendored xcomp confs for all five targets; + - patches 0001–0003. 0003, the iOS `{spawn, LinkedInDriver}` fix, had + only ever been applied by hand. 0002's marker was wrong: upstream + already contains `#ifndef NO_DAEMON`, so the patch was skipped. + - the static `asn1rt_nif.a` via OTP's `static_lib` target; + - `--disable-year2038` on arm32; + - the iOS xmerl deps regeneration. + + The `5c9c69fc` tarballs could not have been reproduced from this repo + alone. +3. Never put host OTP apps, or shims of OTP apps, on the device. Only the + app, its deps and EEx (Elixir stdlib) are pushed or copied. Everything + else comes from the runtime, which ships ssl, public_key, asn1, + runtime_tools and crypto on every target. + - Deploys and builds delete the host-ssl and shim files an older mob_dev + left behind, because a deploy otherwise only adds files. + - `patch_epmd_source/1` now checks for "already patched" at the + `run_daemon(g)` call site, for the same reason patch 0002's marker had + to change. +4. Require `mob ~> 0.9.25`. Older mob hard-codes `erts-17.0` in the Android + launcher, and in the iOS-sim fallback used when `-DERTS_VSN` isn't + passed, so it can't boot erts-17.1.1. mob 0.9.25 reads the directory at + runtime. As a Hex requirement it keeps an app's mob and mob_dev in step + without a doctor check. + +## Consequences + +- Moving to the new runtime means updating both deps and doing a `--native` + deploy. Apps pinned to an older mob_dev keep `5c9c69fc`. +- The host's OTP version no longer affects the device's ssl, public_key or + asn1. That also removes a source of host/device version skew, e.g. ssl from + a 28.x host running on a 29 runtime. +- OpenSSL 3.5.x point releases and OTP 29.x patch releases are now a rebuild + from this repo plus a manifest bump. MOB-495's scan reports when one is + needed. diff --git a/lib/mix/tasks/mob.doctor.ex b/lib/mix/tasks/mob.doctor.ex index bdc0ed00..3aa23729 100644 --- a/lib/mix/tasks/mob.doctor.ex +++ b/lib/mix/tasks/mob.doctor.ex @@ -175,7 +175,7 @@ defmodule Mix.Tasks.Mob.Doctor do {:fail, "OTP", "#{otp} — OTP #{@min_otp} or later required", elixir_upgrade_hint()} n < @warn_otp -> - {:warn, "OTP", "#{otp} — OTP #{@warn_otp}+ recommended (device runtime is OTP 28)", + {:warn, "OTP", "#{otp} — OTP #{@warn_otp}+ recommended (device runtime is OTP 29)", elixir_upgrade_hint()} true -> diff --git a/lib/mob_dev/deployer.ex b/lib/mob_dev/deployer.ex index daabb6cb..a7f46c79 100644 --- a/lib/mob_dev/deployer.ex +++ b/lib/mob_dev/deployer.ex @@ -867,6 +867,11 @@ defmodule MobDev.Deployer do :rooted -> run_adb(["-s", serial, "shell", "mkdir -p #{android_beams_dir()}"]) + case stale_host_ssl_rm_cmd(android_beams_dir()) do + nil -> :ok + cmd -> run_adb(["-s", serial, "shell", cmd]) + end + # Labelled by deploy_android's final relabel_otp_android/1. Enum.reduce_while(beam_dirs, :ok, fn dir, _ -> case run_adb(["-s", serial, "push", "#{Path.expand(dir)}/.", "#{android_beams_dir()}/"]) do @@ -922,6 +927,11 @@ defmodule MobDev.Deployer do "run-as #{android_package()} mkdir -p #{android_beams_dir()}" ]) + case stale_host_ssl_rm_cmd(android_beams_dir()) do + nil -> :ok + cmd -> run_adb(["-s", serial, "shell", "run-as #{android_package()} sh -c '#{cmd}'"]) + end + # Redirect stderr and always exit 0: Android's Toybox tar cannot chown to # macOS UID 501 and exits 1, but the files are extracted correctly. cmd = @@ -1048,6 +1058,7 @@ defmodule MobDev.Deployer do try do File.mkdir_p!(ios_beams_dir()) + remove_stale_host_ssl!(ios_beams_dir()) # Use rsync rather than cp -r for two reasons that hit Nix users hard: # @@ -1955,12 +1966,11 @@ defmodule MobDev.Deployer do eex_ebin = Path.join(to_string(:code.lib_dir(:eex)), "ebin") stdlib_dirs = if File.dir?(eex_ebin), do: [eex_ebin], else: [] - # ssl is a required OTP app (thousand_island lists it as a dependency) but the - # iOS and Android OTP builds omit it. ssl is pure Erlang (no NIFs), so host - # BEAM files run identically on both targets. For HTTP-only Phoenix at loopback, - # ssl starts but no TLS sockets are opened. - ssl_ebin = Path.join(to_string(:code.lib_dir(:ssl)), "ebin") - ssl_dirs = if File.dir?(ssl_ebin), do: [ssl_ebin], else: [] + # Never push the host's OTP apps (ssl was, until MOB-493). The flat + # beams_dir is first on -pa, so a host ssl shadows the runtime's patched + # lib/ssl-*: every device ran the developer's ssl, e.g. OTP 29.0's 11.7 + # with CVE-2026-89422, whatever runtime it had. Every runtime tarball + # ships ssl, public_key and crypto. # crypto is a required OTP app. Older device-side OTP builds (pre-2026-05) # were configured `--without-ssl` to skip OpenSSL, so the device runtime @@ -1983,7 +1993,48 @@ defmodule MobDev.Deployer do end end - app_dirs ++ stdlib_dirs ++ ssl_dirs ++ shim_dirs + app_dirs ++ stdlib_dirs ++ shim_dirs + end + + @doc false + # The files a pre-MOB-493 mob_dev pushed from the host's ssl app into the + # flat beams dir. A deploy only adds files, so on a device deployed before + # then they stay and keep shadowing the runtime's lib/ssl-*; every deploy + # deletes them first. The host that pushed them may have run another OTP + # than this one, so the names come from this host's ssl ebin and from every + # cached runtime's ssl ebin (beam/app/appup). + @spec stale_host_ssl_files() :: [String.t()] + def stale_host_ssl_files do + host = + case :code.lib_dir(:ssl) do + dir when is_list(dir) -> [Path.join(to_string(dir), "ebin")] + _ -> [] + end + + runtimes = Path.wildcard(Path.join([System.user_home!(), ".mob/cache/otp-*/lib/ssl-*/ebin"])) + + (host ++ runtimes) + |> Enum.flat_map(fn ebin -> + case File.ls(ebin) do + {:ok, names} -> names + _ -> [] + end + end) + |> Enum.filter(&(Path.extname(&1) in [".beam", ".app", ".appup"])) + |> Enum.uniq() + |> Enum.sort() + end + + @doc false + @spec stale_host_ssl_rm_cmd(String.t(), [String.t()]) :: String.t() | nil + def stale_host_ssl_rm_cmd(beams_dir, files \\ stale_host_ssl_files()) + def stale_host_ssl_rm_cmd(_beams_dir, []), do: nil + + def stale_host_ssl_rm_cmd(beams_dir, files), + do: ~s(cd "#{beams_dir}" && rm -f #{Enum.join(files, " ")}) + + defp remove_stale_host_ssl!(beams_dir) do + Enum.each(stale_host_ssl_files(), &File.rm(Path.join(beams_dir, &1))) end # Detects whether any cached device-side OTP runtime under ~/.mob/cache/ diff --git a/lib/mob_dev/native_build.ex b/lib/mob_dev/native_build.ex index 323c6c6a..74902a40 100644 --- a/lib/mob_dev/native_build.ex +++ b/lib/mob_dev/native_build.ex @@ -1984,7 +1984,6 @@ defmodule MobDev.NativeBuild do :ok <- install_nx_eigen_otp_lib(otp_root), :ok <- maybe_setup_pythonx_sim(otp_root, erts_vsn, sdkroot, python_bundle, app_module), :ok <- maybe_install_crypto_shim(otp_root, app_module), - :ok <- maybe_copy_ssl_beams(otp_root, app_module), :ok <- maybe_build_phoenix_assets(otp_root, app_module), :ok <- copy_priv_repo_assets(otp_root, app_module), :ok <- copy_elixir_stdlib_to_otp(elixir_lib, otp_root), @@ -2066,6 +2065,9 @@ defmodule MobDev.NativeBuild do File.mkdir_p!(beams_dir) chmod_writable(beams_dir) + # Host ssl BEAMs an older mob_dev copied here shadow the runtime's ssl. + Enum.each(MobDev.Deployer.stale_host_ssl_files(), &File.rm(Path.join(beams_dir, &1))) + # The generated mob_app_config.beam (the project's config/*.exs for the # device, see MobDev.AppConfig) lands in the app's own ebin, which the # wildcard below copies with everything else. @@ -2605,41 +2607,6 @@ defmodule MobDev.NativeBuild do "\n" end - defp maybe_copy_ssl_beams(otp_root, app_module) do - if liveview_project?() do - # thousand_island lists :ssl as a required app. Pure Erlang — host - # macOS .beam files run unchanged on iOS sim. We grab the latest - # available ssl-* dir from the host OTP install. - home = System.get_env("HOME") || "" - host_ssl_dirs = Path.wildcard(home <> "/.local/share/mise/installs/erlang/*/lib/ssl-*") - latest_ssl = host_ssl_dirs |> Enum.sort(:desc) |> List.first() - - case latest_ssl do - nil -> - IO.puts(" === ssl beams not found in host OTP -- thousand_island may fail to start") - :ok - - host_ssl -> - IO.puts(" === Copying ssl beams from host OTP") - beams_dir = Path.join(otp_root, app_module) - ebin = Path.join(host_ssl, "ebin") - - if File.dir?(ebin) do - Path.wildcard("#{ebin}/*.beam") - |> Enum.each(&File.cp!(&1, Path.join(beams_dir, Path.basename(&1)))) - - ssl_app = Path.join(ebin, "ssl.app") - if File.exists?(ssl_app), do: File.cp!(ssl_app, Path.join(beams_dir, "ssl.app")) - IO.puts(" * ssl copied from #{host_ssl}") - end - - :ok - end - else - :ok - end - end - defp maybe_build_phoenix_assets(otp_root, app_module) do if liveview_project?() do IO.puts(" === Building Phoenix static assets") @@ -3691,134 +3658,6 @@ defmodule MobDev.NativeBuild do :ok end - defp maybe_install_ssl_shim(otp_root, app_module) do - if liveview_project?() do - IO.puts(" === Creating ssl shim (LV)") - ssl_tmp = Path.join(System.tmp_dir!(), "mob_ssl_#{System.unique_integer([:positive])}") - File.mkdir_p!(ssl_tmp) - beams_dir = Path.join(otp_root, app_module) - - try do - File.write!(Path.join(ssl_tmp, "ssl.erl"), ssl_shim_erl()) - - case System.cmd("erlc", ["-o", beams_dir, Path.join(ssl_tmp, "ssl.erl")], - stderr_to_stdout: true, - into: IO.stream() - ) do - {_, 0} -> - File.write!(Path.join(beams_dir, "ssl.app"), ssl_shim_app()) - :ok - - {_, _} -> - {:error, "ssl shim erlc failed"} - end - after - File.rm_rf!(ssl_tmp) - end - else - :ok - end - end - - defp ssl_shim_erl do - """ - -module(ssl). - -behaviour(application). - -export([start/2, stop/1, start/0, stop/0, - connect/3, connect/4, connect/5, - listen/2, accept/2, accept/3, - close/1, send/2, recv/2, recv/3, - controlling_process/2, getopts/2, setopts/2, - peername/1, sockname/1, peercert/1, - negotiated_protocol/1, cipher_suites/0, - cipher_suites/2, cipher_suites/3, - versions/0, format_error/1, - clear_pem_cache/0, handshake/1, handshake/2, handshake/3, - handshake_continue/2, handshake_continue/3, - handshake_cancel/1, shutdown/2, - transport_info/1, connection_information/1, - connection_information/2]). - start(_Type, _Args) -> - Pid = spawn(fun() -> receive stop -> ok end end), - {ok, Pid}. - stop(_State) -> ok. - start() -> ok. - stop() -> ok. - connect(_, _, _) -> {error, ssl_not_supported}. - connect(_, _, _, _) -> {error, ssl_not_supported}. - connect(_, _, _, _, _) -> {error, ssl_not_supported}. - listen(_, _) -> {error, ssl_not_supported}. - accept(_, _) -> {error, ssl_not_supported}. - accept(_, _, _) -> {error, ssl_not_supported}. - close(_) -> ok. - send(_, _) -> {error, closed}. - recv(_, _) -> {error, closed}. - recv(_, _, _) -> {error, closed}. - controlling_process(_, _) -> ok. - getopts(_, _) -> {ok, []}. - setopts(_, _) -> ok. - peername(_) -> {error, ssl_not_supported}. - sockname(_) -> {error, ssl_not_supported}. - peercert(_) -> {error, ssl_not_supported}. - negotiated_protocol(_) -> {error, ssl_not_supported}. - cipher_suites() -> []. - cipher_suites(_, _) -> []. - cipher_suites(_, _, _) -> []. - versions() -> []. - format_error(_) -> "ssl not available on iOS (HTTP-only)". - clear_pem_cache() -> ok. - handshake(_) -> {error, ssl_not_supported}. - handshake(_, _) -> {error, ssl_not_supported}. - handshake(_, _, _) -> {error, ssl_not_supported}. - handshake_continue(_, _) -> {error, ssl_not_supported}. - handshake_continue(_, _, _) -> {error, ssl_not_supported}. - handshake_cancel(_) -> ok. - shutdown(_, _) -> ok. - transport_info(_) -> {error, ssl_not_supported}. - connection_information(_) -> {error, ssl_not_supported}. - connection_information(_, _) -> {error, ssl_not_supported}. - """ - end - - defp ssl_shim_app do - ~S|{application,ssl,[{modules,[ssl]},{applications,[kernel,stdlib,crypto,public_key]},{description,"SSL shim for iOS (HTTP-only)"},{registered,[]},{vsn,"11.2"},{mod,{ssl,[]}}]}.| <> - "\n" - end - - defp copy_otp_libs_for_phoenix(otp_root) do - IO.puts(" === Copying OTP standard libraries (Phoenix deps)") - # Phoenix and its deps require runtime_tools (extra_applications), - # asn1 (public_key dep), and public_key (cookie/cert infra). The Mob - # iOS-device tarball does not bundle these; copy from the host. - for app <- ~w(runtime_tools asn1 public_key) do - case :code.lib_dir(String.to_atom(app)) do - src when is_list(src) -> - src_dir = to_string(src) - ebin = Path.join(src_dir, "ebin") - - if File.dir?(ebin) do - vsn_dir = Path.basename(src_dir) - dst_ebin = Path.join([otp_root, "lib", vsn_dir, "ebin"]) - File.mkdir_p!(dst_ebin) - - Path.wildcard("#{ebin}/*.beam") - |> Enum.each(&File.cp!(&1, Path.join(dst_ebin, Path.basename(&1)))) - - app_file = Path.join(ebin, "#{app}.app") - if File.exists?(app_file), do: File.cp!(app_file, Path.join(dst_ebin, "#{app}.app")) - IO.puts(" + #{vsn_dir}") - else - IO.puts(" ! #{app} not found on host — skipping") - end - - _ -> - IO.puts(" ! #{app} not found on host — skipping") - end - end - - :ok - end - defp install_app_in_otp_lib(otp_root, app_module) do # Plug.Static (from: :app_name) resolves the priv dir via code:lib_dir/1, # which requires a code-path entry named "app_name-vsn" (not just @@ -3875,32 +3714,35 @@ defmodule MobDev.NativeBuild do # but the call site still references the symbol, so the link fails # with "Undefined symbols: _run_daemon". Idempotent inline patch wraps # the call in `#ifndef NO_DAEMON` so both halves go away together. + # + # Already-patched is detected at the call site: upstream epmd.c has its + # own `#ifndef NO_DAEMON` around run_daemon's *definition*, so looking + # for that string anywhere (as this did before MOB-493) skipped the patch. epmd_c = Path.join([epmd_build_src, "erts/epmd/src/epmd.c"]) + guarded = Regex.compile!("#ifndef NO_DAEMON\\n\\s*run_daemon\\(g\\);") - cond do - not File.exists?(epmd_c) -> - :ok - - File.read!(epmd_c) =~ "ifndef NO_DAEMON" -> - :ok - - true -> - IO.puts(" patching #{epmd_c} (NO_DAEMON guard around run_daemon call)") - original = File.read!(epmd_c) + unguarded = + Regex.compile!( + "( if \\(g->is_daemon\\) \\{\\n)(\\trun_daemon\\(g\\);\\n)( \\} else \\{\\n)" + ) - # Match the exact signature from the OTP source. - pattern = ~r/( if \(g->is_daemon\) \{\n)(\trun_daemon\(g\);\n)( \} else \{\n)/ + with true <- File.exists?(epmd_c), + original = File.read!(epmd_c), + false <- Regex.match?(guarded, original) do + IO.puts(" patching #{epmd_c} (NO_DAEMON guard around run_daemon call)") - case Regex.replace(pattern, original, "\\1#ifndef NO_DAEMON\n\\2#endif\n\\3", - global: false - ) do - ^original -> - {:error, "epmd.c patch pattern did not match — manual fix required"} + case Regex.replace(unguarded, original, "\\1#ifndef NO_DAEMON\n\\2#endif\n\\3", + global: false + ) do + ^original -> + {:error, "epmd.c patch pattern did not match — manual fix required"} - patched -> - File.write!(epmd_c, patched) - :ok - end + patched -> + File.write!(epmd_c, patched) + :ok + end + else + _ -> :ok end end @@ -4871,10 +4713,8 @@ defmodule MobDev.NativeBuild do :ok <- maybe_setup_pythonx_device(otp_root, erts_vsn, sdkroot, python_bundle, app_module), :ok <- maybe_install_crypto_shim(otp_root, app_module), - :ok <- maybe_install_ssl_shim(otp_root, app_module), :ok <- copy_elixir_stdlib_to_otp(elixir_lib, otp_root), :ok <- copy_eex_stdlib_to_app(elixir_lib, otp_root, app_module), - :ok <- copy_otp_libs_for_phoenix(otp_root), :ok <- copy_priv_repo_assets(otp_root, app_module), :ok <- maybe_build_phoenix_assets(otp_root, app_module), :ok <- install_app_in_otp_lib(otp_root, app_module), diff --git a/lib/mob_dev/otp_downloader.ex b/lib/mob_dev/otp_downloader.ex index 8e3d3f8c..f52af830 100644 --- a/lib/mob_dev/otp_downloader.ex +++ b/lib/mob_dev/otp_downloader.ex @@ -5,7 +5,7 @@ defmodule MobDev.OtpDownloader do Artifacts are cached at `~/.mob/cache/` and reused across projects. """ - @otp_hash "5c9c69fc" + @otp_hash "d199a49a" @release_tag "otp-#{@otp_hash}" @base_url "https://github.com/GenericJam/mob/releases/download/#{@release_tag}" diff --git a/lib/mob_dev/release.ex b/lib/mob_dev/release.ex index 2b307f86..4674157b 100644 --- a/lib/mob_dev/release.ex +++ b/lib/mob_dev/release.ex @@ -760,6 +760,9 @@ defmodule MobDev.Release do echo "=== Copying BEAM files to $BEAMS_DIR ===" mkdir -p "$BEAMS_DIR" + # A dev device build by mob_dev < 0.7.30 compiled a no-op ssl shim into + # this (reused) dir for LiveView apps; on -pa it shadows lib/ssl-*. + rm -f "$BEAMS_DIR/ssl.beam" "$BEAMS_DIR/ssl.app" for lib_dir in _build/dev/lib/*/ebin; do cp "$lib_dir"/* "$BEAMS_DIR/" 2>/dev/null || true done @@ -811,21 +814,10 @@ defmodule MobDev.Release do cp "$ELIXIR_LIB/eex/ebin/"*.beam "$BEAMS_DIR/" 2>/dev/null || true cp "$ELIXIR_LIB/eex/ebin/eex.app" "$BEAMS_DIR/" 2>/dev/null || true - copy_otp_lib() { - local APP="$1" - local SRC - SRC=$(elixir -e "IO.puts(:code.lib_dir(:${APP}))" 2>/dev/null) - if [ -n "$SRC" ] && [ -d "$SRC/ebin" ]; then - local VSN - VSN=$(basename "$SRC") - mkdir -p "$OTP_ROOT/lib/$VSN/ebin" - cp "$SRC/ebin/"*.beam "$OTP_ROOT/lib/$VSN/ebin/" - cp "$SRC/ebin/${APP}.app" "$OTP_ROOT/lib/$VSN/ebin/" - fi - } - copy_otp_lib runtime_tools - copy_otp_lib asn1 - copy_otp_lib public_key + # runtime_tools, asn1 and public_key come from the runtime tarball (every + # current one ships them). They used to be copied from the host here, + # which could replace the runtime's patched public_key with the + # build machine's (MOB-493). echo "=== Copying priv (migrations, assets, bundled ebins, app priv) ===" if [ -d "assets" ]; then diff --git a/lib/mob_dev/release_android.ex b/lib/mob_dev/release_android.ex index 14ea57ae..e958670d 100644 --- a/lib/mob_dev/release_android.ex +++ b/lib/mob_dev/release_android.ex @@ -170,17 +170,15 @@ defmodule MobDev.ReleaseAndroid do end # Same runtime BEAM collection as deployer.collect_beam_dirs/0: app + deps + - # eex (Elixir stdlib, not in _build/) + ssl (Thousand Island dep, not in OTP tree). + # eex (Elixir stdlib, not in _build/). No host OTP apps: the runtime ships + # ssl, and a host copy here shadowed it in every release (MOB-493). defp collect_beam_dirs do app_dirs = MobDev.HotPush.runtime_beam_dirs() eex_ebin = Path.join(to_string(:code.lib_dir(:eex)), "ebin") eex = if File.dir?(eex_ebin), do: [eex_ebin], else: [] - ssl_ebin = Path.join(to_string(:code.lib_dir(:ssl)), "ebin") - ssl = if File.dir?(ssl_ebin), do: [ssl_ebin], else: [] - - app_dirs ++ eex ++ ssl + app_dirs ++ eex end defp exqlite_version, do: MobDev.AppFile.dep_version(:exqlite) diff --git a/lib/mob_dev/security_scan/bundled_runtime/fingerprint.ex b/lib/mob_dev/security_scan/bundled_runtime/fingerprint.ex index b8039121..dc70c281 100644 --- a/lib/mob_dev/security_scan/bundled_runtime/fingerprint.ex +++ b/lib/mob_dev/security_scan/bundled_runtime/fingerprint.ex @@ -21,7 +21,7 @@ defmodule MobDev.SecurityScan.BundledRuntime.Fingerprint do OTP commit hash from `MobDev.OtpDownloader`. """ @type tarball :: %{ - platform: :android | :android_arm32 | :ios_sim | :ios_device, + platform: :android | :android_arm32 | :android_x86_64 | :ios_sim | :ios_device, hash: String.t(), path: Path.t() } @@ -71,11 +71,15 @@ defmodule MobDev.SecurityScan.BundledRuntime.Fingerprint do # Regex.compile!/1 (not ~r/.../) to sidestep OTP 28.0's `:re.import/1` # undefined-function bug on sigil-precompiled regexes loaded from beam # files. See MobDev.NdkVersion.project_pinned/1 for the same workaround. - pattern = Regex.compile!("^otp-(android-arm32|android|ios-sim|ios-device)-([0-9a-f]+)$") + pattern = + Regex.compile!( + "^otp-(android-arm32|android-x86_64|android|ios-sim|ios-device)-([0-9a-f]+)$" + ) case Regex.run(pattern, Path.basename(path)) do [_, "android", hash] -> [%{platform: :android, hash: hash, path: path}] [_, "android-arm32", hash] -> [%{platform: :android_arm32, hash: hash, path: path}] + [_, "android-x86_64", hash] -> [%{platform: :android_x86_64, hash: hash, path: path}] [_, "ios-sim", hash] -> [%{platform: :ios_sim, hash: hash, path: path}] [_, "ios-device", hash] -> [%{platform: :ios_device, hash: hash, path: path}] _ -> [] diff --git a/mix.exs b/mix.exs index 98564ff1..55da996d 100644 --- a/mix.exs +++ b/mix.exs @@ -73,7 +73,12 @@ defmodule MobDev.MixProject do # by the SAME function `Mob.Theme.font/2` uses on the runtime side. # Before this, mob_dev had its own copy of the normalization logic, # kept in sync with mob core's copy only by convention — see MOB_FONTS.md. - {:mob, "~> 0.7.25 or ~> 0.8.1 or ~> 0.9.0", runtime: false}, + # + # >= 0.9.25 because the OTP runtime this mob_dev downloads (d199a49a, + # erts-17.1.1) only boots on a mob that finds its ERTS dir at runtime; + # older mob hard-codes erts-17.0 on Android and the iOS simulator + # (MOB-493). Hex resolution then keeps an app's mob and mob_dev in step. + {:mob, "~> 0.9.25", runtime: false}, {:eqrcode, "~> 0.2"}, {:jason, "~> 1.4"}, {:mix_audit, "~> 2.1", runtime: false}, diff --git a/priv/security/bundled_versions.exs b/priv/security/bundled_versions.exs index e67c436b..b423d8fa 100644 --- a/priv/security/bundled_versions.exs +++ b/priv/security/bundled_versions.exs @@ -22,8 +22,27 @@ # is bundled at deploy time instead. %{ - active_hash: "5c9c69fc", + active_hash: "d199a49a", bundles: %{ + # Rebuilt from the OTP-29.1.2 tag (commit d199a49a) with OpenSSL 3.5.9 + # (MOB-493/494): every OTP security fix through 2026-10-14 and the 3.5 + # LTS branch. erts 17.0 -> 17.1.1. Same Elixir and exqlite BEAMs as + # 5c9c69fc (exqlite 0.37.0 on android_x86_64, as before). + "d199a49a" => %{ + erts: "17.1.1", + otp_release: "29", + otp_version: "29.1.2", + elixir: "1.20.1", + openssl: "3.5.9", + exqlite_beam: "0.36.0", + openssl_release_date: "2026-09-29", + platforms: [:android, :android_arm32, :android_x86_64, :ios_sim, :ios_device], + per_platform: %{ + android_x86_64: %{exqlite_beam: "0.37.0"}, + ios_sim: %{exqlite_beam: nil}, + ios_device: %{exqlite_beam: nil} + } + }, # Same OTP-29 / erts-17.0 / OpenSSL base as 7d46fdd4, Elixir bumped # rc.5 -> 1.20.1 (stdlib swap; published as otp-5c9c69fc). "5c9c69fc" => %{ @@ -33,8 +52,11 @@ openssl: "3.4.0", exqlite_beam: "0.36.0", openssl_release_date: "2024-10-22", - platforms: [:android, :android_arm32, :ios_sim, :ios_device], + platforms: [:android, :android_arm32, :android_x86_64, :ios_sim, :ios_device], + # The published android_x86_64 tarball carries exqlite 0.37.0 (checked + # 2026-10-22 against the release asset); fingerprinted since MOB-493. per_platform: %{ + android_x86_64: %{exqlite_beam: "0.37.0"}, ios_sim: %{exqlite_beam: nil}, ios_device: %{exqlite_beam: nil} } diff --git a/scripts/release/openssl/README.md b/scripts/release/openssl/README.md index 1a9152c1..2a4757a1 100644 --- a/scripts/release/openssl/README.md +++ b/scripts/release/openssl/README.md @@ -23,15 +23,18 @@ gotchas (BSD `ar`'s empty-archive trap, Android `RTLD_LOCAL`). ## Source -OpenSSL 3.4.0, cloned shallow at `~/code/openssl`: +OpenSSL 3.5.9, cloned shallow at `~/code/openssl`: ```bash -git clone --depth 1 --branch openssl-3.4.0 https://github.com/openssl/openssl.git ~/code/openssl +git clone --depth 1 --branch openssl-3.5.9 https://github.com/openssl/openssl.git ~/code/openssl ``` -Pinning to 3.4.0 (not 3.5.x) for now because 3.4 is the latest stable -LTS. Bump after running each script clean against a newer tag and -verifying tarball boots end-to-end. +3.5 is the current LTS branch (supported until 2030-04-08). Before +2026-10 this pinned 3.4.0 and never took a 3.4.x point release; 3.4 went +end-of-life on 2026-10-22 with 55 CVEs unpatched in 3.4.0 (MOB-494). +Take every 3.5.x point release: bump the tag here, rebuild all targets, +and update `openssl` / `openssl_release_date` in +`priv/security/bundled_versions.exs`. ## Configure flags (all targets) diff --git a/scripts/release/openssl/_build_otp_android_arm64.sh b/scripts/release/openssl/_build_otp_android_arm64.sh index a1b71ebd..20f4dacf 100755 --- a/scripts/release/openssl/_build_otp_android_arm64.sh +++ b/scripts/release/openssl/_build_otp_android_arm64.sh @@ -19,8 +19,18 @@ export PATH="$NDK_ROOT/toolchains/llvm/prebuilt/darwin-x86_64/bin:$PATH" export NDK_ABI_PLAT export RELEASE_LIBBEAM=yes +# Resolved before the cd below: $0 may be relative. +VENDORED_CONF="$(cd "$(dirname "$0")/../xcomp" && pwd)/erl-xcomp-arm64-android.conf" + cd "$OTP_SRC" +# Install mob's arm64 xcomp conf over upstream's stub (llvm-ar, 16 KB pages, +# -Os/gc-sections, trimmed apps) — see the header of +# scripts/release/xcomp/erl-xcomp-arm64-android.conf. Same pattern as the +# x86_64 sibling. +[ -f "$VENDORED_CONF" ] || { echo "missing $VENDORED_CONF" >&2; exit 1; } +cp "$VENDORED_CONF" "$OTP_SRC/xcomp/erl-xcomp-arm64-android.conf" + # Clean any stale config from a prior arch (iOS, etc.). make distclean >/dev/null 2>&1 || true @@ -31,6 +41,11 @@ make distclean >/dev/null 2>&1 || true ./otp_build boot +# Static asn1 NIF for the tarball's erts-*/lib/asn1rt_nif.a (linked into the +# app like crypto.a). `boot` only builds it with --enable-static-nifs, which +# Android doesn't use, so build OTP's own static_lib target directly. +ERL_TOP="$OTP_SRC" make -C lib/asn1/c_src TARGET=aarch64-unknown-linux-android static_lib + # Stage the install tree at /tmp/otp-android. rm -rf "$OTP_RELEASE" ./otp_build release -a "$OTP_RELEASE" diff --git a/scripts/release/openssl/_build_otp_android_x86_64.sh b/scripts/release/openssl/_build_otp_android_x86_64.sh index 4cc1436c..014b5969 100755 --- a/scripts/release/openssl/_build_otp_android_x86_64.sh +++ b/scripts/release/openssl/_build_otp_android_x86_64.sh @@ -20,6 +20,10 @@ export PATH="$NDK_ROOT/toolchains/llvm/prebuilt/darwin-x86_64/bin:$PATH" export NDK_ABI_PLAT export RELEASE_LIBBEAM=yes +# Resolved before the cd below: $0 may be relative, and a relative path +# made the old `[ -f ] && cp` skip the conf silently. +VENDORED_CONF="$(cd "$(dirname "$0")/../xcomp" && pwd)/erl-xcomp-x86_64-android.conf" + cd "$OTP_SRC" # Install mob's corrected x86_64 xcomp conf into the OTP source. Upstream @@ -27,8 +31,8 @@ cd "$OTP_SRC" # --without-* flags; references the removed ld.gold) — see # scripts/release/xcomp/erl-xcomp-x86_64-android.conf. Vendored here so a # fresh OTP checkout builds without manual patching. -VENDORED_CONF="$(dirname "$0")/../xcomp/erl-xcomp-x86_64-android.conf" -[ -f "$VENDORED_CONF" ] && cp "$VENDORED_CONF" "$OTP_SRC/xcomp/erl-xcomp-x86_64-android.conf" +[ -f "$VENDORED_CONF" ] || { echo "missing $VENDORED_CONF" >&2; exit 1; } +cp "$VENDORED_CONF" "$OTP_SRC/xcomp/erl-xcomp-x86_64-android.conf" # Clean any stale config from a prior arch (arm64, iOS, etc.). @@ -41,6 +45,11 @@ make distclean >/dev/null 2>&1 || true ./otp_build boot +# Static asn1 NIF for the tarball's erts-*/lib/asn1rt_nif.a (linked into the +# app like crypto.a). `boot` only builds it with --enable-static-nifs, which +# Android doesn't use, so build OTP's own static_lib target directly. +ERL_TOP="$OTP_SRC" make -C lib/asn1/c_src TARGET=x86_64-pc-linux-android static_lib + rm -rf "$OTP_RELEASE" ./otp_build release -a "$OTP_RELEASE" diff --git a/scripts/release/patches/0003-ios-spawn-linked-in-driver.patch b/scripts/release/patches/0003-ios-spawn-linked-in-driver.patch new file mode 100644 index 00000000..32f37776 --- /dev/null +++ b/scripts/release/patches/0003-ios-spawn-linked-in-driver.patch @@ -0,0 +1,45 @@ +iOS: allow {spawn, Name} to open a linked-in driver (mob_dev iOS spawn patch). + +Upstream erts_open_driver returns BADARG on iOS for any spawn_type with the +EXECUTABLE bit before looking the name up among linked-in drivers, so plain +{spawn, "ram_file_drv"} fails. That breaks file:open(_, [ram]), in-memory +erl_tar extract, hex_tarball:unpack and Mix.install on iOS. Move the guard +after the lookup so it only fires when no linked-in driver matched. + +Previously applied by hand to the 7d46fdd4 build; recorded here so rebuilds +carry it. + +diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c +index 24468ad..3c76b61 100644 +--- a/erts/emulator/beam/io.c ++++ b/erts/emulator/beam/io.c +@@ -588,12 +588,7 @@ erts_open_driver(erts_driver_t* driver, /* Pointer to driver. */ + if (!(opts->spawn_type & ERTS_SPAWN_EXECUTABLE)) { + /* No spawn driver default */ + driver = NULL; +- } else { +-#ifdef __IOS__ +- erts_rwmtx_runlock(&erts_driver_list_lock); +- ERTS_OPEN_DRIVER_RET(NULL, -3, BADARG); +-#endif +- } ++ } + + + if (opts->spawn_type != ERTS_SPAWN_EXECUTABLE) { +@@ -620,6 +615,15 @@ erts_open_driver(erts_driver_t* driver, /* Pointer to driver. */ + if (p != NULL) + *p = ' '; + } ++#ifdef __IOS__ ++ /* mob_dev iOS spawn patch: iOS can't fork/exec, so only a linked-in ++ * driver is openable. Refuse here, after the driver-name lookup, so ++ * {spawn, "ram_file_drv"} etc. still resolve. */ ++ if (driver == &spawn_driver) { ++ erts_rwmtx_runlock(&erts_driver_list_lock); ++ ERTS_OPEN_DRIVER_RET(NULL, -3, BADARG); ++ } ++#endif + } + + if (driver == NULL || (driver != &spawn_driver && opts->exit_status)) { diff --git a/scripts/release/tarball_android_arm32.sh b/scripts/release/tarball_android_arm32.sh index 9d263eeb..2aac098b 100755 --- a/scripts/release/tarball_android_arm32.sh +++ b/scripts/release/tarball_android_arm32.sh @@ -2,15 +2,13 @@ # scripts/release/tarball_android_arm32.sh # Stage and tar the Android arm32 (armeabi-v7a) OTP runtime + exqlite BEAMs. # Mirrors Step 2 (arm32) of build_release.md. -# -# Prerequisite: a separately-built arm32 asn1rt_nif.a at -# $ASN1RT_NIF_ARM32 (default /tmp/asn1rt_nif_arm32.a — see build_release.md -# Step 2 arm32 prerequisites for the exact NDK-clang invocation). +# Same layout as the arm64 sibling. asn1rt_nif.a comes from the OTP build +# (xcompile_android_arm32.sh runs OTP's static_lib target for it). # # Inputs (env or default): # OTP_SRC — OTP source checkout (default: ~/code/otp) # OTP_RELEASE — Android arm32 install dir (default: /tmp/otp-android-arm32) -# ASN1RT_NIF_ARM32 — pre-built arm32 asn1rt_nif.a (default: /tmp/asn1rt_nif_arm32.a) +# ASN1RT_NIF_ARM32 — arm32 asn1rt_nif.a (default: the OTP build's) # EXQLITE_BUILD, HASH, OUT_DIR — see _lib.sh / arm64 script # # Output: @@ -22,11 +20,11 @@ cd "$(dirname "$0")" source ./_lib.sh : "${OTP_RELEASE:=/tmp/otp-android-arm32}" -: "${ASN1RT_NIF_ARM32:=/tmp/asn1rt_nif_arm32.a}" +: "${ASN1RT_NIF_ARM32:=$OTP_SRC/lib/asn1/priv/lib/arm-unknown-linux-androideabi/asn1rt_nif.a}" : "${EXQLITE_BUILD:=}" [ -d "$OTP_RELEASE" ] || fail "missing $OTP_RELEASE — cross-compile Android arm32 first" -[ -f "$ASN1RT_NIF_ARM32" ] || fail "missing $ASN1RT_NIF_ARM32 — build it per build_release.md Step 2 arm32 prerequisites" +[ -f "$ASN1RT_NIF_ARM32" ] || fail "missing $ASN1RT_NIF_ARM32 — run xcompile_android_arm32.sh first" [ -n "$EXQLITE_BUILD" ] || fail "EXQLITE_BUILD not set" [ -d "$EXQLITE_BUILD/ebin" ] || fail "EXQLITE_BUILD ($EXQLITE_BUILD) has no ebin/" diff --git a/scripts/release/xcomp/erl-xcomp-arm-android.conf b/scripts/release/xcomp/erl-xcomp-arm-android.conf new file mode 100644 index 00000000..f491115c --- /dev/null +++ b/scripts/release/xcomp/erl-xcomp-arm-android.conf @@ -0,0 +1,316 @@ +## -*-shell-script-*- +## +## %CopyrightBegin% +## +## SPDX-License-Identifier: Apache-2.0 +## +## Copyright Ericsson AB 2009-2025. All Rights Reserved. +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## %CopyrightEnd% +## +## File: erl-xcomp-arm-android.conf +## Author: Dmitry Kolesnikov +## +## mob_dev: vendored from OTP-29.1.2 with the settings mob's runtime needs +## (same as erl-xcomp-x86_64-android.conf next to it): llvm-ar/llvm-ranlib +## (macOS /usr/bin/ar silently writes empty archives for ELF objects, so +## libei.a came out empty and erl_call failed to link), -Os with +## gc-sections, 16 KB page alignment, static libc++, and no +## debugger/observer/et/cdv. Upstream's stub has none of these; the 29.0 +## tarballs were built from a hand-edited copy that was never committed. +## ----------------------------------------------------------------------------- +## When cross compiling Erlang/OTP using `otp_build', copy this file and set +## the variables needed below. Then pass the path to the copy of this file as +## an argument to `otp_build' in the configure stage: +## `otp_build configure --xcomp-conf=' +## ----------------------------------------------------------------------------- + +## Note that you cannot define arbitrary variables in a cross compilation +## configuration file. Only the ones listed below will be guaranteed to be +## visible throughout the whole execution of all `configure' scripts. Other +## variables needs to be defined as arguments to `configure' or exported in +## the environment. + +## -- Variables for `otp_build' Only ------------------------------------------- + +## Variables in this section are only used, when configuring Erlang/OTP for +## cross compilation using `$ERL_TOP/otp_build configure'. + +## *NOTE*! These variables currently have *no* effect if you configure using +## the `configure' script directly. + +# * `erl_xcomp_build' - The build system used. This value will be passed as +# `--build=$erl_xcomp_build' argument to the `configure' script. It does +# not have to be a full `CPU-VENDOR-OS' triplet, but can be. The full +# `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/make/autoconf/config.sub $erl_xcomp_build'. If set to `guess', +# the build system will be guessed using +# `$ERL_TOP/make/autoconf/config.guess'. +erl_xcomp_build=guess + +# * `erl_xcomp_host' - Cross host/target system to build for. This value will +# be passed as `--host=$erl_xcomp_host' argument to the `configure' script. +# It does not have to be a full `CPU-VENDOR-OS' triplet, but can be. The +# full `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/make/autoconf/config.sub $erl_xcomp_host'. +erl_xcomp_host=arm-linux-androideabi + +# * `erl_xcomp_configure_flags' - Extra configure flags to pass to the +# `configure' script. +# Set --enable-builtin-zlib to avoid a inflateGetDictionary missing symbol +# mob_dev: --disable-year2038 because 32-bit bionic's time_t is 32 bits and +# can't be widened; OTP 29.1's configure refuses to continue without it. +erl_xcomp_configure_flags="--without-termcap --without-wx \ + --without-debugger --without-observer --without-et --without-cdv \ + --enable-builtin-zlib --enable-deterministic-build \ + --disable-year2038" + + +## -- Cross Compiler and Other Tools ------------------------------------------- + +## +## +# Previously with older Android NDK versions before NDK r14, each Android +# API level had a different set of headers, each in its own directory. +#NDK_SYSROOT=$NDK_ROOT/platforms/$NDK_PLAT/arch-arm +# Starting with Android NDK r14, a single set of headers (called Unified +# Headers) is provided in one location for every Android API level. +#NDK_SYSROOT=$NDK_ROOT/sysroot +# Starting with Android NDK r19, the actual sysroot path is not needed +# anymore as the NDK toolchain handles it implicitly. +# For reference: https://github.com/android/ndk/issues/1407 +#NDK_SYSROOT=$NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/sysroot + +## If the cross compilation tools are prefixed by `-' you probably do +## not need to set these variables (where `' is what has been passed as +## `--host=' argument to `configure'). + +## All variables in this section can also be used when native compiling. + +# * `CC' - C compiler. +# +# For older Android NDK versions still supporting GCC. +#CC="arm-linux-androideabi-gcc --sysroot=$NDK_SYSROOT" +# For more recent Android NDK versions only supporting Clang/LLVM. +#NDK_ABI_PLAT=androideabi16 # when targeting Android 4.1 Jelly Bean +CC="armv7a-linux-$NDK_ABI_PLAT-clang" + +# * `CFLAGS' - C compiler flags. +CFLAGS="-g -Os -ffunction-sections -fdata-sections -march=armv7-a -mfloat-abi=softfp -mthumb" + +# * `STATIC_CFLAGS' - Static C compiler flags. +#STATIC_CFLAGS= + +# * `CFLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for the shared libraries. Note that this actually is a +# linker flag, but it needs to be passed via the compiler. +#CFLAG_RUNTIME_LIBRARY_PATH= + +# * `CPP' - C pre-processor. +# +# For older Android NDK versions still supporting GCC. +#CPP="arm-linux-androideabi-cpp --sysroot=$NDK_SYSROOT" +# Not needed when using Clang/LLVM. +#CPP= + +# * `CPPFLAGS' - C pre-processor flags. +# +# For older Android NDK versions still supporting GCC. +#CPPFLAGS="-static -march=armv7-a -msoft-float -mthumb" +# Not needed when using Clang/LLVM. +#CPPFLAGS= + +# * `CXX' - C++ compiler. +# +# For older Android NDK versions still supporting GCC. +#CXX="arm-linux-androideabi-c++ --sysroot=$NDK_SYSROOT +# For more recent Android NDK versions only supporting Clang/LLVM. +CXX="armv7a-linux-$NDK_ABI_PLAT-clang++" + +# * `CXXFLAGS' - C++ compiler flags. +CXXFLAGS="-march=armv7-a -mfloat-abi=softfp -mthumb" + +# * `LD' - Linker. +# +# Not needed when using GCC. +#LD= +# For more recent Android NDK versions only supporting Clang/LLVM. +LD="arm-linux-androideabi-ld" + +# * `LDFLAGS' - Linker flags. +LDFLAGS="-march=armv7-a -mfloat-abi=softfp -mthumb -static-libstdc++ -Wl,--gc-sections -Wl,-z,max-page-size=16384" + +# * `LIBS' - Libraries. +#LIBS= + +## -- *D*ynamic *E*rlang *D*river Linking -- + +## *NOTE*! Either set all or none of the `DED_LD*' variables. + +# * `DED_LD' - Linker for Dynamically loaded Erlang Drivers. +#DED_LD= + +# * `DED_LDFLAGS' - Linker flags to use with `DED_LD'. +#DED_LDFLAGS= + +# * `DED_LD_FLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for shared libraries when linking with `DED_LD'. +#DED_LD_FLAG_RUNTIME_LIBRARY_PATH= + +## -- Large File Support -- + +## *NOTE*! Either set all or none of the `LFS_*' variables. + +# * `LFS_CFLAGS' - Large file support C compiler flags. +#LFS_CFLAGS= + +# * `LFS_LDFLAGS' - Large file support linker flags. +#LFS_LDFLAGS= + +# * `LFS_LIBS' - Large file support libraries. +#LFS_LIBS= + +## -- Other Tools -- + +# * `RANLIB' - `ranlib' archive index tool. +RANLIB="llvm-ranlib" + +# * `AR' - `ar' archiving tool. +AR="llvm-ar" + +# * `GETCONF' - `getconf' system configuration inspection tool. `getconf' is +# currently used for finding out large file support flags to use, and +# on Linux systems for finding out if we have an NPTL thread library or +# not. +#GETCONF= + +## -- Cross System Root Locations ---------------------------------------------- + +# * `erl_xcomp_sysroot' - The absolute path to the system root of the cross +# compilation environment. Currently, the `crypto', `odbc', `ssh' and +# `ssl' applications need the system root. These applications will be +# skipped if the system root has not been set. The system root might be +# needed for other things too. If this is the case and the system root +# has not been set, `configure' will fail and request you to set it. +# +# Previously for older Android NDK versions before NDK r19. +#erl_xcomp_sysroot="$NDK_SYSROOT" +# Starting with Android NDK r19, this path does not matter anymore +# as the NDK toolchain handles the sysroot directory implicitly. +# Set a value anyway to enable all applications as described above. +erl_xcomp_sysroot=/sysroot/path/handled/by/the/Android/NDK + + +# * `erl_xcomp_isysroot' - The absolute path to the system root for includes +# of the cross compilation environment. If not set, this value defaults +# to `$erl_xcomp_sysroot', i.e., only set this value if the include system +# root path is not the same as the system root path. +#erl_xcomp_isysroot= + +## -- Optional Feature, and Bug Tests ------------------------------------------ + +## These tests cannot (always) be done automatically when cross compiling. You +## usually do not need to set these variables. Only set these if you really +## know what you are doing. + +## Note that some of these values will override results of tests performed +## by `configure', and some will not be used until `configure' is sure that +## it cannot figure the result out. + +## The `configure' script will issue a warning when a default value is used. +## When a variable has been set, no warning will be issued. + +# * `erl_xcomp_after_morecore_hook' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `__after_morecore_hook' that can be +# used for tracking used `malloc()' implementations core memory usage. +# This is currently only used by unsupported features. +#erl_xcomp_after_morecore_hook= + +# * `erl_xcomp_bigendian' - `yes|no'. No default. If `yes', the target system +# must be big endian. If `no', little endian. This can often be +# automatically detected, but not always. If not automatically detected, +# `configure' will fail unless this variable is set. Since no default +# value is used, `configure' will try to figure this out automatically. +#erl_xcomp_bigendian= + +# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `clock_gettime()' implementation +# that can be used for retrieving process CPU time. +#erl_xcomp_clock_gettime_cpu_time= + +# * `erl_xcomp_getaddrinfo' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `getaddrinfo()' implementation that can +# handle both IPv4 and IPv6. +#erl_xcomp_getaddrinfo= + +# * `erl_xcomp_gethrvtime_procfs_ioctl' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `gethrvtime()' implementation and +# is used with procfs `ioctl()'. +#erl_xcomp_gethrvtime_procfs_ioctl= + +# * `erl_xcomp_dlsym_brk_wrappers' - `yes|no'. Defaults to `no'. If `yes', the +# target system must have a working `dlsym(RTLD_NEXT, )' implementation +# that can be used on `brk' and `sbrk' symbols used by the `malloc()' +# implementation in use, and by this track the `malloc()' implementations +# core memory usage. This is currently only used by unsupported features. +#erl_xcomp_dlsym_brk_wrappers= + +# * `erl_xcomp_kqueue' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `kqueue()' implementation that returns a file +# descriptor which can be used by `poll()' and/or `select()'. If `no' and +# the target system has not got `epoll()' or `/dev/poll', the kernel-poll +# feature will be disabled. +#erl_xcomp_kqueue= + +# * `erl_xcomp_linux_clock_gettime_correction' - `yes|no'. Defaults to `yes' on +# Linux; otherwise, `no'. If `yes', `clock_gettime(CLOCK_MONOTONIC, _)' on +# the target system must work. This variable is recommended to be set to +# `no' on Linux systems with kernel versions less than 2.6. +#erl_xcomp_linux_clock_gettime_correction= + +# * `erl_xcomp_linux_nptl' - `yes|no'. Defaults to `yes' on Linux; otherwise, +# `no'. If `yes', the target system must have NPTL (Native POSIX Thread +# Library). Older Linux systems have LinuxThreads instead of NPTL (Linux +# kernel versions typically less than 2.6). +#erl_xcomp_linux_nptl= + +# * `erl_xcomp_linux_usable_sigaltstack' - `yes|no'. Defaults to `yes' on Linux; +# otherwise, `no'. If `yes', `sigaltstack()' must be usable on the target +# system. `sigaltstack()' on Linux kernel versions less than 2.4 are +# broken. +#erl_xcomp_linux_usable_sigaltstack= + +# * `erl_xcomp_linux_usable_sigusrx' - `yes|no'. Defaults to `yes'. If `yes', +# the `SIGUSR1' and `SIGUSR2' signals must be usable by the ERTS. Old +# LinuxThreads thread libraries (Linux kernel versions typically less than +# 2.2) used these signals and made them unusable by the ERTS. +#erl_xcomp_linux_usable_sigusrx= + +# * `erl_xcomp_poll' - `yes|no'. Defaults to `no' on Darwin/MacOSX; otherwise, +# `yes'. If `yes', the target system must have a working `poll()' +# implementation that also can handle devices. If `no', `select()' will be +# used instead of `poll()'. +#erl_xcomp_poll= + +# * `erl_xcomp_putenv_copy' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a `putenv()' implementation that stores a copy of the +# key/value pair. +#erl_xcomp_putenv_copy= + +# * `erl_xcomp_reliable_fpe' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have reliable floating point exceptions. +#erl_xcomp_reliable_fpe= + +## ----------------------------------------------------------------------------- diff --git a/scripts/release/xcomp/erl-xcomp-arm64-android.conf b/scripts/release/xcomp/erl-xcomp-arm64-android.conf new file mode 100644 index 00000000..9752bb03 --- /dev/null +++ b/scripts/release/xcomp/erl-xcomp-arm64-android.conf @@ -0,0 +1,305 @@ +## -*-shell-script-*- +## +## %CopyrightBegin% +## +## SPDX-License-Identifier: Apache-2.0 +## +## Copyright Ericsson AB 2019-2025. All Rights Reserved. +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## %CopyrightEnd% +## +## File: erl-xcomp-arm64-android.conf +## Author: Jérôme de Bretagne +## +## mob_dev: vendored from OTP-29.1.2 with the settings mob's runtime needs +## (same as erl-xcomp-x86_64-android.conf next to it): llvm-ar/llvm-ranlib +## (macOS /usr/bin/ar silently writes empty archives for ELF objects, so +## libei.a came out empty and erl_call failed to link), -Os with +## gc-sections, 16 KB page alignment (Android 15+ / Play requirement), and +## no debugger/observer/et/cdv. Upstream's stub has none of these; the 29.0 +## tarballs were built from a hand-edited copy that was never committed. +## ----------------------------------------------------------------------------- +## When cross compiling Erlang/OTP using `otp_build', copy this file and set +## the variables needed below. Then pass the path to the copy of this file as +## an argument to `otp_build' in the configure stage: +## `otp_build configure --xcomp-conf=' +## ----------------------------------------------------------------------------- + +## Note that you cannot define arbitrary variables in a cross compilation +## configuration file. Only the ones listed below will be guaranteed to be +## visible throughout the whole execution of all `configure' scripts. Other +## variables needs to be defined as arguments to `configure' or exported in +## the environment. + +## -- Variables for `otp_build' Only ------------------------------------------- + +## Variables in this section are only used, when configuring Erlang/OTP for +## cross compilation using `$ERL_TOP/otp_build configure'. + +## *NOTE*! These variables currently have *no* effect if you configure using +## the `configure' script directly. + +# * `erl_xcomp_build' - The build system used. This value will be passed as +# `--build=$erl_xcomp_build' argument to the `configure' script. It does +# not have to be a full `CPU-VENDOR-OS' triplet, but can be. The full +# `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/make/autoconf/config.sub $erl_xcomp_build'. If set to `guess', +# the build system will be guessed using +# `$ERL_TOP/make/autoconf/config.guess'. +erl_xcomp_build=guess + +# * `erl_xcomp_host' - Cross host/target system to build for. This value will +# be passed as `--host=$erl_xcomp_host' argument to the `configure' script. +# It does not have to be a full `CPU-VENDOR-OS' triplet, but can be. The +# full `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/make/autoconf/config.sub $erl_xcomp_host'. +erl_xcomp_host=aarch64-linux-android + +# * `erl_xcomp_configure_flags' - Extra configure flags to pass to the +# `configure' script. +# Set --enable-builtin-zlib to avoid a inflateGetDictionary missing symbol +erl_xcomp_configure_flags="--without-termcap --without-wx \ + --without-debugger --without-observer --without-et --without-cdv \ + --enable-builtin-zlib --enable-deterministic-build" + + +## -- Cross Compiler and Other Tools ------------------------------------------- + +## +## +# Previously with older Android NDK versions before NDK r14, each Android +# API level had a different set of headers, each in its own directory. +#NDK_SYSROOT=$NDK_ROOT/platforms/$NDK_PLAT/arch-arm +# Starting with Android NDK r14, a single set of headers (called Unified +# Headers) is provided in one location for every Android API level. +#NDK_SYSROOT=$NDK_ROOT/sysroot +# Starting with Android NDK r19, the actual sysroot path is not needed +# anymore as the NDK toolchain handles it implicitly. +# For reference: https://github.com/android/ndk/issues/1407 +#NDK_SYSROOT=$NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/sysroot + +## If the cross compilation tools are prefixed by `-' you probably do +## not need to set these variables (where `' is what has been passed as +## `--host=' argument to `configure'). + +## All variables in this section can also be used when native compiling. + +# * `CC' - C compiler. +# +# For recent Android NDK versions only supporting Clang/LLVM. +#NDK_ABI_PLAT=android21 # when targeting Android 5.0 Lollipop +CC="aarch64-linux-$NDK_ABI_PLAT-clang" + +# * `CFLAGS' - C compiler flags. +CFLAGS="-g -Os -ffunction-sections -fdata-sections" + +# * `STATIC_CFLAGS' - Static C compiler flags. +#STATIC_CFLAGS= + +# * `CFLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for the shared libraries. Note that this actually is a +# linker flag, but it needs to be passed via the compiler. +#CFLAG_RUNTIME_LIBRARY_PATH= + +# * `CPP' - C pre-processor. +# +# Not needed when using Clang/LLVM. +#CPP= + +# * `CPPFLAGS' - C pre-processor flags. +# +# Not needed when using Clang/LLVM. +#CPPFLAGS= + +# * `CXX' - C++ compiler. +# +# For recent Android NDK versions only supporting Clang/LLVM. +CXX="aarch64-linux-$NDK_ABI_PLAT-clang++" + +# * `CXXFLAGS' - C++ compiler flags. +#CXXFLAGS= + +# * `LD' - Linker. +# +# For recent Android NDK versions only supporting Clang/LLVM. +LD="aarch64-linux-android-ld" + +# * `LDFLAGS' - Linker flags. +# Use the static version of libc++ provided by the Android NDK +# when compiling the JIT flavor of the beam.smp executable. +LDFLAGS="-static-libstdc++ -Wl,--gc-sections -Wl,-z,max-page-size=16384" + +# * `LIBS' - Libraries. +#LIBS= + +## -- *D*ynamic *E*rlang *D*river Linking -- + +## *NOTE*! Either set all or none of the `DED_LD*' variables. + +# * `DED_LD' - Linker for Dynamically loaded Erlang Drivers. +#DED_LD= + +# * `DED_LDFLAGS' - Linker flags to use with `DED_LD'. +#DED_LDFLAGS= + +# * `DED_LD_FLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for shared libraries when linking with `DED_LD'. +#DED_LD_FLAG_RUNTIME_LIBRARY_PATH= + +## -- Large File Support -- + +## *NOTE*! Either set all or none of the `LFS_*' variables. + +# * `LFS_CFLAGS' - Large file support C compiler flags. +#LFS_CFLAGS= + +# * `LFS_LDFLAGS' - Large file support linker flags. +#LFS_LDFLAGS= + +# * `LFS_LIBS' - Large file support libraries. +#LFS_LIBS= + +## -- Other Tools -- + +# * `RANLIB' - `ranlib' archive index tool. +RANLIB="llvm-ranlib" + +# * `AR' - `ar' archiving tool. +AR="llvm-ar" + +# * `GETCONF' - `getconf' system configuration inspection tool. `getconf' is +# currently used for finding out large file support flags to use, and +# on Linux systems for finding out if we have an NPTL thread library or +# not. +#GETCONF= + +## -- Cross System Root Locations ---------------------------------------------- + +# * `erl_xcomp_sysroot' - The absolute path to the system root of the cross +# compilation environment. Currently, the `crypto', `odbc', `ssh' and +# `ssl' applications need the system root. These applications will be +# skipped if the system root has not been set. The system root might be +# needed for other things too. If this is the case and the system root +# has not been set, `configure' will fail and request you to set it. +# +# Previously for older Android NDK versions before NDK r19. +#erl_xcomp_sysroot="$NDK_SYSROOT" +# Starting with Android NDK r19, this path does not matter anymore +# as the NDK toolchain handles the sysroot directory implicitly. +# Set a value anyway to enable all applications as described above. +erl_xcomp_sysroot=/sysroot/path/handled/by/the/Android/NDK + + +# * `erl_xcomp_isysroot' - The absolute path to the system root for includes +# of the cross compilation environment. If not set, this value defaults +# to `$erl_xcomp_sysroot', i.e., only set this value if the include system +# root path is not the same as the system root path. +#erl_xcomp_isysroot= + +## -- Optional Feature, and Bug Tests ------------------------------------------ + +## These tests cannot (always) be done automatically when cross compiling. You +## usually do not need to set these variables. Only set these if you really +## know what you are doing. + +## Note that some of these values will override results of tests performed +## by `configure', and some will not be used until `configure' is sure that +## it cannot figure the result out. + +## The `configure' script will issue a warning when a default value is used. +## When a variable has been set, no warning will be issued. + +# * `erl_xcomp_after_morecore_hook' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `__after_morecore_hook' that can be +# used for tracking used `malloc()' implementations core memory usage. +# This is currently only used by unsupported features. +#erl_xcomp_after_morecore_hook= + +# * `erl_xcomp_bigendian' - `yes|no'. No default. If `yes', the target system +# must be big endian. If `no', little endian. This can often be +# automatically detected, but not always. If not automatically detected, +# `configure' will fail unless this variable is set. Since no default +# value is used, `configure' will try to figure this out automatically. +#erl_xcomp_bigendian= + +# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `clock_gettime()' implementation +# that can be used for retrieving process CPU time. +#erl_xcomp_clock_gettime_cpu_time= + +# * `erl_xcomp_getaddrinfo' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `getaddrinfo()' implementation that can +# handle both IPv4 and IPv6. +#erl_xcomp_getaddrinfo= + +# * `erl_xcomp_gethrvtime_procfs_ioctl' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `gethrvtime()' implementation and +# is used with procfs `ioctl()'. +#erl_xcomp_gethrvtime_procfs_ioctl= + +# * `erl_xcomp_dlsym_brk_wrappers' - `yes|no'. Defaults to `no'. If `yes', the +# target system must have a working `dlsym(RTLD_NEXT, )' implementation +# that can be used on `brk' and `sbrk' symbols used by the `malloc()' +# implementation in use, and by this track the `malloc()' implementations +# core memory usage. This is currently only used by unsupported features. +#erl_xcomp_dlsym_brk_wrappers= + +# * `erl_xcomp_kqueue' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `kqueue()' implementation that returns a file +# descriptor which can be used by `poll()' and/or `select()'. If `no' and +# the target system has not got `epoll()' or `/dev/poll', the kernel-poll +# feature will be disabled. +#erl_xcomp_kqueue= + +# * `erl_xcomp_linux_clock_gettime_correction' - `yes|no'. Defaults to `yes' on +# Linux; otherwise, `no'. If `yes', `clock_gettime(CLOCK_MONOTONIC, _)' on +# the target system must work. This variable is recommended to be set to +# `no' on Linux systems with kernel versions less than 2.6. +#erl_xcomp_linux_clock_gettime_correction= + +# * `erl_xcomp_linux_nptl' - `yes|no'. Defaults to `yes' on Linux; otherwise, +# `no'. If `yes', the target system must have NPTL (Native POSIX Thread +# Library). Older Linux systems have LinuxThreads instead of NPTL (Linux +# kernel versions typically less than 2.6). +#erl_xcomp_linux_nptl= + +# * `erl_xcomp_linux_usable_sigaltstack' - `yes|no'. Defaults to `yes' on Linux; +# otherwise, `no'. If `yes', `sigaltstack()' must be usable on the target +# system. `sigaltstack()' on Linux kernel versions less than 2.4 are +# broken. +#erl_xcomp_linux_usable_sigaltstack= + +# * `erl_xcomp_linux_usable_sigusrx' - `yes|no'. Defaults to `yes'. If `yes', +# the `SIGUSR1' and `SIGUSR2' signals must be usable by the ERTS. Old +# LinuxThreads thread libraries (Linux kernel versions typically less than +# 2.2) used these signals and made them unusable by the ERTS. +#erl_xcomp_linux_usable_sigusrx= + +# * `erl_xcomp_poll' - `yes|no'. Defaults to `no' on Darwin/MacOSX; otherwise, +# `yes'. If `yes', the target system must have a working `poll()' +# implementation that also can handle devices. If `no', `select()' will be +# used instead of `poll()'. +#erl_xcomp_poll= + +# * `erl_xcomp_putenv_copy' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a `putenv()' implementation that stores a copy of the +# key/value pair. +#erl_xcomp_putenv_copy= + +# * `erl_xcomp_reliable_fpe' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have reliable floating point exceptions. +#erl_xcomp_reliable_fpe= + +## ----------------------------------------------------------------------------- diff --git a/scripts/release/xcomp/erl-xcomp-arm64-ios.conf b/scripts/release/xcomp/erl-xcomp-arm64-ios.conf new file mode 100644 index 00000000..e4badc5c --- /dev/null +++ b/scripts/release/xcomp/erl-xcomp-arm64-ios.conf @@ -0,0 +1,275 @@ +## -*-shell-script-*- +## +## %CopyrightBegin% +## +## SPDX-License-Identifier: Apache-2.0 +## +## Copyright Ericsson AB 2009-2025. All Rights Reserved. +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## %CopyrightEnd% +## +## File: erl-xcomp-arm64-ios.conf +## Author: Dominic Letz +## +## mob_dev: vendored from OTP-29.1.2 with one change, DED_LD below. +## ----------------------------------------------------------------------------- +## When cross compiling Erlang/OTP using `otp_build', copy this file and set +## the variables needed below. Then pass the path to the copy of this file as +## an argument to `otp_build' in the configure stage: +## `otp_build configure --xcomp-conf=' +## ----------------------------------------------------------------------------- + +## Note that you cannot define arbitrary variables in a cross compilation +## configuration file. Only the ones listed below will be guaranteed to be +## visible throughout the whole execution of all `configure' scripts. Other +## variables needs to be defined as arguments to `configure' or exported in +## the environment. + +## -- Variables for `otp_build' Only ------------------------------------------- + +## Variables in this section are only used, when configuring Erlang/OTP for +## cross compilation using `$ERL_TOP/otp_build configure'. + +## *NOTE*! These variables currently have *no* effect if you configure using +## the `configure' script directly. + +# * `erl_xcomp_build' - The build system used. This value will be passed as +# `--build=$erl_xcomp_build' argument to the `configure' script. It does +# not have to be a full `CPU-VENDOR-OS' triplet, but can be. The full +# `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/erts/autoconf/config.sub $erl_xcomp_build'. If set to `guess', +# the build system will be guessed using +# `$ERL_TOP/erts/autoconf/config.guess'. +erl_xcomp_build=guess + +# * `erl_xcomp_host' - Cross host/target system to build for. This value will +# be passed as `--host=$erl_xcomp_host' argument to the `configure' script. +# It does not have to be a full `CPU-VENDOR-OS' triplet, but can be. The +# full `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/erts/autoconf/config.sub $erl_xcomp_host'. +erl_xcomp_host=arm64-apple-ios + +# * `erl_xcomp_configure_flags' - Extra configure flags to pass to the +# `configure' script. +erl_xcomp_configure_flags="--without-termcap --without-wx \ + --without-javac --without-odbc \ + --without-debugger --without-observer --without-cdv --without-et \ + --enable-builtin-zlib --enable-static-nifs --enable-static-drivers \ + --disable-jit --disable-security-hardening-flags" + + +## -- Cross Compiler and Other Tools ------------------------------------------- + +## If the cross compilation tools are prefixed by `-' you probably do +## not need to set these variables (where `' is what has been passed as +## `--host=' argument to `configure'). + +## All variables in this section can also be used when native compiling. + +export XCOMP_SDK=iphoneos +export XCOMP_SDKROOT=`xcrun -sdk $XCOMP_SDK --show-sdk-path` +export XCOMP_ARCH="-arch arm64" + +# * `CC' - C compiler. +CC="xcrun -sdk $XCOMP_SDK cc $XCOMP_ARCH" + +# * `CFLAGS' - C compiler flags. +CFLAGS="-mios-version-min=7.0.0 -fno-common -Os -D__IOS__=yes" + +# * `STATIC_CFLAGS' - Static C compiler flags. +#STATIC_CFLAGS= + +# * `CFLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for the shared libraries. Note that this actually is a +# linker flag, but it needs to be passed via the compiler. +#CFLAG_RUNTIME_LIBRARY_PATH= + +# * `CPP' - C pre-processor. +#CPP= + +# * `CPPFLAGS' - C pre-processor flags. +#CPPFLAGS= + +# * `CXX' - C++ compiler. +CXX="xcrun -sdk $XCOMP_SDK c++ $XCOMP_ARCH" + +# * `CXXFLAGS' - C++ compiler flags. +CXXFLAGS=$CFLAGS + +# * `LD' - Linker. +LD="xcrun -sdk $XCOMP_SDK ld $XCOMP_ARCH" + +# * `LDFLAGS' - Linker flags. +LDFLAGS="-L$XCOMP_SDKROOT/usr/lib/ -lc++ -v" + +# * `LIBS' - Libraries. +#LIBS= + +## -- *D*ynamic *E*rlang *D*river Linking -- + +## *NOTE*! Either set all or none of the `DED_LD*' variables. + +# * `DED_LD' - Linker for Dynamically loaded Erlang Drivers. +# mob_dev: the cc driver, not raw ld. OTP 29.1's security hardening adds +# -fstack-protector-strong to DED_LDFLAGS, which `ld` rejects ("unknown +# options") and the driver accepts; `cc -r` still makes a relocatable object. +DED_LD=$CC + +# * `DED_LDFLAGS' - Linker flags to use with `DED_LD'. +DED_LDFLAGS="-L$XCOMP_SDKROOT/usr/lib/ -r -v" + +# * `DED_LD_FLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for shared libraries when linking with `DED_LD'. +#DED_LD_FLAG_RUNTIME_LIBRARY_PATH= + +## -- Large File Support -- + +## *NOTE*! Either set all or none of the `LFS_*' variables. + +# * `LFS_CFLAGS' - Large file support C compiler flags. +#LFS_CFLAGS= + +# * `LFS_LDFLAGS' - Large file support linker flags. +#LFS_LDFLAGS= + +# * `LFS_LIBS' - Large file support libraries. +#LFS_LIBS= + +## -- Other Tools -- + +# * `RANLIB' - `ranlib' archive index tool. +RANLIB="xcrun -sdk $XCOMP_SDK ranlib" + +# * `AR' - `ar' archiving tool. +AR="xcrun -sdk $XCOMP_SDK ar" + +# * `GETCONF' - `getconf' system configuration inspection tool. `getconf' is +# currently used for finding out large file support flags to use, and +# on Linux systems for finding out if we have an NPTL thread library or +# not. +#GETCONF= + +## -- Cross System Root Locations ---------------------------------------------- + +# * `erl_xcomp_sysroot' - The absolute path to the system root of the cross +# compilation environment. Currently, the `crypto', `odbc', `ssh' and +# `ssl' applications need the system root. These applications will be +# skipped if the system root has not been set. The system root might be +# needed for other things too. If this is the case and the system root +# has not been set, `configure' will fail and request you to set it. +erl_xcomp_sysroot=$XCOMP_SDKROOT + + +# * `erl_xcomp_isysroot' - The absolute path to the system root for includes +# of the cross compilation environment. If not set, this value defaults +# to `$erl_xcomp_sysroot', i.e., only set this value if the include system +# root path is not the same as the system root path. +#erl_xcomp_isysroot= + +## -- Optional Feature, and Bug Tests ------------------------------------------ + +## These tests cannot (always) be done automatically when cross compiling. You +## usually do not need to set these variables. Only set these if you really +## know what you are doing. + +## Note that some of these values will override results of tests performed +## by `configure', and some will not be used until `configure' is sure that +## it cannot figure the result out. + +## The `configure' script will issue a warning when a default value is used. +## When a variable has been set, no warning will be issued. + +# * `erl_xcomp_after_morecore_hook' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `__after_morecore_hook' that can be +# used for tracking used `malloc()' implementations core memory usage. +# This is currently only used by unsupported features. +#erl_xcomp_after_morecore_hook= + +# * `erl_xcomp_bigendian' - `yes|no'. No default. If `yes', the target system +# must be big endian. If `no', little endian. This can often be +# automatically detected, but not always. If not automatically detected, +# `configure' will fail unless this variable is set. Since no default +# value is used, `configure' will try to figure this out automatically. +#erl_xcomp_bigendian= + +# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `clock_gettime()' implementation +# that can be used for retrieving process CPU time. +#erl_xcomp_clock_gettime_cpu_time= + +# * `erl_xcomp_getaddrinfo' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `getaddrinfo()' implementation that can +# handle both IPv4 and IPv6. +#erl_xcomp_getaddrinfo= + +# * `erl_xcomp_gethrvtime_procfs_ioctl' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `gethrvtime()' implementation and +# is used with procfs `ioctl()'. +#erl_xcomp_gethrvtime_procfs_ioctl= + +# * `erl_xcomp_dlsym_brk_wrappers' - `yes|no'. Defaults to `no'. If `yes', the +# target system must have a working `dlsym(RTLD_NEXT, )' implementation +# that can be used on `brk' and `sbrk' symbols used by the `malloc()' +# implementation in use, and by this track the `malloc()' implementations +# core memory usage. This is currently only used by unsupported features. +#erl_xcomp_dlsym_brk_wrappers= + +# * `erl_xcomp_kqueue' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `kqueue()' implementation that returns a file +# descriptor which can be used by `poll()' and/or `select()'. If `no' and +# the target system has not got `epoll()' or `/dev/poll', the kernel-poll +# feature will be disabled. +#erl_xcomp_kqueue= + +# * `erl_xcomp_linux_clock_gettime_correction' - `yes|no'. Defaults to `yes' on +# Linux; otherwise, `no'. If `yes', `clock_gettime(CLOCK_MONOTONIC, _)' on +# the target system must work. This variable is recommended to be set to +# `no' on Linux systems with kernel versions less than 2.6. +#erl_xcomp_linux_clock_gettime_correction= + +# * `erl_xcomp_linux_nptl' - `yes|no'. Defaults to `yes' on Linux; otherwise, +# `no'. If `yes', the target system must have NPTL (Native POSIX Thread +# Library). Older Linux systems have LinuxThreads instead of NPTL (Linux +# kernel versions typically less than 2.6). +#erl_xcomp_linux_nptl= + +# * `erl_xcomp_linux_usable_sigaltstack' - `yes|no'. Defaults to `yes' on Linux; +# otherwise, `no'. If `yes', `sigaltstack()' must be usable on the target +# system. `sigaltstack()' on Linux kernel versions less than 2.4 are +# broken. +#erl_xcomp_linux_usable_sigaltstack= + +# * `erl_xcomp_linux_usable_sigusrx' - `yes|no'. Defaults to `yes'. If `yes', +# the `SIGUSR1' and `SIGUSR2' signals must be usable by the ERTS. Old +# LinuxThreads thread libraries (Linux kernel versions typically less than +# 2.2) used these signals and made them unusable by the ERTS. +#erl_xcomp_linux_usable_sigusrx= + +# * `erl_xcomp_poll' - `yes|no'. Defaults to `no' on Darwin/MacOSX; otherwise, +# `yes'. If `yes', the target system must have a working `poll()' +# implementation that also can handle devices. If `no', `select()' will be +# used instead of `poll()'. +#erl_xcomp_poll= + +# * `erl_xcomp_putenv_copy' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a `putenv()' implementation that stores a copy of the +# key/value pair. +#erl_xcomp_putenv_copy= + +# * `erl_xcomp_reliable_fpe' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have reliable floating point exceptions. +#erl_xcomp_reliable_fpe= + +## ----------------------------------------------------------------------------- diff --git a/scripts/release/xcomp/erl-xcomp-arm64-iossimulator.conf b/scripts/release/xcomp/erl-xcomp-arm64-iossimulator.conf new file mode 100644 index 00000000..8a5b9587 --- /dev/null +++ b/scripts/release/xcomp/erl-xcomp-arm64-iossimulator.conf @@ -0,0 +1,275 @@ +## -*-shell-script-*- +## +## %CopyrightBegin% +## +## SPDX-License-Identifier: Apache-2.0 +## +## Copyright Ericsson AB 2009-2025. All Rights Reserved. +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## %CopyrightEnd% +## +## File: erl-xcomp-arm64-iossimulator.conf +## Author: Dominic Letz +## +## mob_dev: vendored from OTP-29.1.2 with one change, DED_LD below. +## ----------------------------------------------------------------------------- +## When cross compiling Erlang/OTP using `otp_build', copy this file and set +## the variables needed below. Then pass the path to the copy of this file as +## an argument to `otp_build' in the configure stage: +## `otp_build configure --xcomp-conf=' +## ----------------------------------------------------------------------------- + +## Note that you cannot define arbitrary variables in a cross compilation +## configuration file. Only the ones listed below will be guaranteed to be +## visible throughout the whole execution of all `configure' scripts. Other +## variables needs to be defined as arguments to `configure' or exported in +## the environment. + +## -- Variables for `otp_build' Only ------------------------------------------- + +## Variables in this section are only used, when configuring Erlang/OTP for +## cross compilation using `$ERL_TOP/otp_build configure'. + +## *NOTE*! These variables currently have *no* effect if you configure using +## the `configure' script directly. + +# * `erl_xcomp_build' - The build system used. This value will be passed as +# `--build=$erl_xcomp_build' argument to the `configure' script. It does +# not have to be a full `CPU-VENDOR-OS' triplet, but can be. The full +# `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/erts/autoconf/config.sub $erl_xcomp_build'. If set to `guess', +# the build system will be guessed using +# `$ERL_TOP/erts/autoconf/config.guess'. +erl_xcomp_build=guess + +# * `erl_xcomp_host' - Cross host/target system to build for. This value will +# be passed as `--host=$erl_xcomp_host' argument to the `configure' script. +# It does not have to be a full `CPU-VENDOR-OS' triplet, but can be. The +# full `CPU-VENDOR-OS' triplet will be created by +# `$ERL_TOP/erts/autoconf/config.sub $erl_xcomp_host'. +erl_xcomp_host=arm64-apple-iossimulator + +# * `erl_xcomp_configure_flags' - Extra configure flags to pass to the +# `configure' script. +erl_xcomp_configure_flags="--without-termcap --without-wx \ + --without-javac --without-odbc \ + --without-debugger --without-observer --without-cdv --without-et \ + --enable-builtin-zlib --enable-static-nifs --enable-static-drivers \ + --disable-jit" + + +## -- Cross Compiler and Other Tools ------------------------------------------- + +## If the cross compilation tools are prefixed by `-' you probably do +## not need to set these variables (where `' is what has been passed as +## `--host=' argument to `configure'). + +## All variables in this section can also be used when native compiling. + +export XCOMP_SDK=iphonesimulator +export XCOMP_SDKROOT=`xcrun -sdk $XCOMP_SDK --show-sdk-path` +export XCOMP_ARCH="-arch arm64" + +# * `CC' - C compiler. +CC="xcrun -sdk $XCOMP_SDK cc $XCOMP_ARCH" + +# * `CFLAGS' - C compiler flags. +CFLAGS="-mios-simulator-version-min=7.0.0 -fno-common -Os -D__IOS__=yes" + +# * `STATIC_CFLAGS' - Static C compiler flags. +#STATIC_CFLAGS= + +# * `CFLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for the shared libraries. Note that this actually is a +# linker flag, but it needs to be passed via the compiler. +#CFLAG_RUNTIME_LIBRARY_PATH= + +# * `CPP' - C pre-processor. +#CPP= + +# * `CPPFLAGS' - C pre-processor flags. +#CPPFLAGS= + +# * `CXX' - C++ compiler. +CXX="xcrun -sdk $XCOMP_SDK c++ $XCOMP_ARCH" + +# * `CXXFLAGS' - C++ compiler flags. +CXXFLAGS=$CFLAGS + +# * `LD' - Linker. +LD="xcrun -sdk $XCOMP_SDK ld $XCOMP_ARCH" + +# * `LDFLAGS' - Linker flags. +LDFLAGS="-L$XCOMP_SDKROOT/usr/lib/ -lc++ -v" + +# * `LIBS' - Libraries. +#LIBS= + +## -- *D*ynamic *E*rlang *D*river Linking -- + +## *NOTE*! Either set all or none of the `DED_LD*' variables. + +# * `DED_LD' - Linker for Dynamically loaded Erlang Drivers. +# mob_dev: the cc driver, not raw ld. OTP 29.1's security hardening adds +# -fstack-protector-strong to DED_LDFLAGS, which `ld` rejects ("unknown +# options") and the driver accepts; `cc -r` still makes a relocatable object. +DED_LD=$CC + +# * `DED_LDFLAGS' - Linker flags to use with `DED_LD'. +DED_LDFLAGS="-L$XCOMP_SDKROOT/usr/lib/ -r -v" + +# * `DED_LD_FLAG_RUNTIME_LIBRARY_PATH' - This flag should set runtime library +# search path for shared libraries when linking with `DED_LD'. +#DED_LD_FLAG_RUNTIME_LIBRARY_PATH= + +## -- Large File Support -- + +## *NOTE*! Either set all or none of the `LFS_*' variables. + +# * `LFS_CFLAGS' - Large file support C compiler flags. +#LFS_CFLAGS= + +# * `LFS_LDFLAGS' - Large file support linker flags. +#LFS_LDFLAGS= + +# * `LFS_LIBS' - Large file support libraries. +#LFS_LIBS= + +## -- Other Tools -- + +# * `RANLIB' - `ranlib' archive index tool. +RANLIB="xcrun -sdk $XCOMP_SDK ranlib" + +# * `AR' - `ar' archiving tool. +AR="xcrun -sdk $XCOMP_SDK ar" + +# * `GETCONF' - `getconf' system configuration inspection tool. `getconf' is +# currently used for finding out large file support flags to use, and +# on Linux systems for finding out if we have an NPTL thread library or +# not. +#GETCONF= + +## -- Cross System Root Locations ---------------------------------------------- + +# * `erl_xcomp_sysroot' - The absolute path to the system root of the cross +# compilation environment. Currently, the `crypto', `odbc', `ssh' and +# `ssl' applications need the system root. These applications will be +# skipped if the system root has not been set. The system root might be +# needed for other things too. If this is the case and the system root +# has not been set, `configure' will fail and request you to set it. +erl_xcomp_sysroot=$XCOMP_SDKROOT + + +# * `erl_xcomp_isysroot' - The absolute path to the system root for includes +# of the cross compilation environment. If not set, this value defaults +# to `$erl_xcomp_sysroot', i.e., only set this value if the include system +# root path is not the same as the system root path. +#erl_xcomp_isysroot= + +## -- Optional Feature, and Bug Tests ------------------------------------------ + +## These tests cannot (always) be done automatically when cross compiling. You +## usually do not need to set these variables. Only set these if you really +## know what you are doing. + +## Note that some of these values will override results of tests performed +## by `configure', and some will not be used until `configure' is sure that +## it cannot figure the result out. + +## The `configure' script will issue a warning when a default value is used. +## When a variable has been set, no warning will be issued. + +# * `erl_xcomp_after_morecore_hook' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `__after_morecore_hook' that can be +# used for tracking used `malloc()' implementations core memory usage. +# This is currently only used by unsupported features. +#erl_xcomp_after_morecore_hook= + +# * `erl_xcomp_bigendian' - `yes|no'. No default. If `yes', the target system +# must be big endian. If `no', little endian. This can often be +# automatically detected, but not always. If not automatically detected, +# `configure' will fail unless this variable is set. Since no default +# value is used, `configure' will try to figure this out automatically. +#erl_xcomp_bigendian= + +# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `clock_gettime()' implementation +# that can be used for retrieving process CPU time. +#erl_xcomp_clock_gettime_cpu_time= + +# * `erl_xcomp_getaddrinfo' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `getaddrinfo()' implementation that can +# handle both IPv4 and IPv6. +#erl_xcomp_getaddrinfo= + +# * `erl_xcomp_gethrvtime_procfs_ioctl' - `yes|no'. Defaults to `no'. If `yes', +# the target system must have a working `gethrvtime()' implementation and +# is used with procfs `ioctl()'. +#erl_xcomp_gethrvtime_procfs_ioctl= + +# * `erl_xcomp_dlsym_brk_wrappers' - `yes|no'. Defaults to `no'. If `yes', the +# target system must have a working `dlsym(RTLD_NEXT, )' implementation +# that can be used on `brk' and `sbrk' symbols used by the `malloc()' +# implementation in use, and by this track the `malloc()' implementations +# core memory usage. This is currently only used by unsupported features. +#erl_xcomp_dlsym_brk_wrappers= + +# * `erl_xcomp_kqueue' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a working `kqueue()' implementation that returns a file +# descriptor which can be used by `poll()' and/or `select()'. If `no' and +# the target system has not got `epoll()' or `/dev/poll', the kernel-poll +# feature will be disabled. +#erl_xcomp_kqueue= + +# * `erl_xcomp_linux_clock_gettime_correction' - `yes|no'. Defaults to `yes' on +# Linux; otherwise, `no'. If `yes', `clock_gettime(CLOCK_MONOTONIC, _)' on +# the target system must work. This variable is recommended to be set to +# `no' on Linux systems with kernel versions less than 2.6. +#erl_xcomp_linux_clock_gettime_correction= + +# * `erl_xcomp_linux_nptl' - `yes|no'. Defaults to `yes' on Linux; otherwise, +# `no'. If `yes', the target system must have NPTL (Native POSIX Thread +# Library). Older Linux systems have LinuxThreads instead of NPTL (Linux +# kernel versions typically less than 2.6). +#erl_xcomp_linux_nptl= + +# * `erl_xcomp_linux_usable_sigaltstack' - `yes|no'. Defaults to `yes' on Linux; +# otherwise, `no'. If `yes', `sigaltstack()' must be usable on the target +# system. `sigaltstack()' on Linux kernel versions less than 2.4 are +# broken. +#erl_xcomp_linux_usable_sigaltstack= + +# * `erl_xcomp_linux_usable_sigusrx' - `yes|no'. Defaults to `yes'. If `yes', +# the `SIGUSR1' and `SIGUSR2' signals must be usable by the ERTS. Old +# LinuxThreads thread libraries (Linux kernel versions typically less than +# 2.2) used these signals and made them unusable by the ERTS. +#erl_xcomp_linux_usable_sigusrx= + +# * `erl_xcomp_poll' - `yes|no'. Defaults to `no' on Darwin/MacOSX; otherwise, +# `yes'. If `yes', the target system must have a working `poll()' +# implementation that also can handle devices. If `no', `select()' will be +# used instead of `poll()'. +#erl_xcomp_poll= + +# * `erl_xcomp_putenv_copy' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have a `putenv()' implementation that stores a copy of the +# key/value pair. +#erl_xcomp_putenv_copy= + +# * `erl_xcomp_reliable_fpe' - `yes|no'. Defaults to `no'. If `yes', the target +# system must have reliable floating point exceptions. +#erl_xcomp_reliable_fpe= + +## ----------------------------------------------------------------------------- diff --git a/scripts/release/xcompile_android_arm32.sh b/scripts/release/xcompile_android_arm32.sh index 1a015512..38182700 100755 --- a/scripts/release/xcompile_android_arm32.sh +++ b/scripts/release/xcompile_android_arm32.sh @@ -53,6 +53,13 @@ export RELEASE_LIBBEAM=yes cd "$OTP_SRC" +# Install mob's arm32 xcomp conf over upstream's stub (llvm-ar, 16 KB pages, +# -Os/gc-sections, trimmed apps) — see the header of +# scripts/release/xcomp/erl-xcomp-arm-android.conf. +VENDORED_CONF="$SCRIPT_DIR/xcomp/erl-xcomp-arm-android.conf" +[ -f "$VENDORED_CONF" ] || fail "missing $VENDORED_CONF" +cp "$VENDORED_CONF" "$OTP_SRC/xcomp/erl-xcomp-arm-android.conf" + # Clean any stale config from a prior arch (iOS, arm64, etc.). make distclean >/dev/null 2>&1 || true @@ -72,6 +79,10 @@ log "configuring for arm-unknown-linux-androideabi..." log "building (this takes ~5–10 min)..." ./otp_build boot +# Static asn1 NIF for the tarball's erts-*/lib/asn1rt_nif.a, as the arm64 and +# x86_64 scripts do. (This used to be a manual NDK clang step.) +ERL_TOP="$OTP_SRC" make -C lib/asn1/c_src TARGET=arm-unknown-linux-androideabi static_lib + log "installing to $RELEASE_ROOT..." rm -rf "$RELEASE_ROOT" ./otp_build release -a "$RELEASE_ROOT" diff --git a/scripts/release/xcompile_ios_device.sh b/scripts/release/xcompile_ios_device.sh index 42ea3edd..df6023a6 100755 --- a/scripts/release/xcompile_ios_device.sh +++ b/scripts/release/xcompile_ios_device.sh @@ -62,13 +62,26 @@ apply_patch "$PATCHES_DIR/0001-ios-device-skip-forker-fork.patch" \ "mob_dev iOS device patch" \ erts/emulator/sys/unix/sys_drivers.c +# The marker is the patch's own comment: upstream epmd.c already contains +# `#ifndef NO_DAEMON` (around run_daemon's definition), so checking for +# that skipped the patch and left the call site unguarded. apply_patch "$PATCHES_DIR/0002-ios-device-epmd-no-daemon.patch" \ - "ifndef NO_DAEMON" \ + "NO_DAEMON strips run_daemon" \ erts/epmd/src/epmd.c +# __IOS__-guarded, so it is also applied by xcompile_ios_sim.sh. +apply_patch "$PATCHES_DIR/0003-ios-spawn-linked-in-driver.patch" \ + "mob_dev iOS spawn patch" \ + erts/emulator/beam/io.c + # iOS doesn't allow shared libraries; emit static libbeam.a instead of .so. export RELEASE_LIBBEAM=yes +# mob's device xcomp conf (DED_LD through the cc driver) over upstream's. +VENDORED_CONF="$SCRIPT_DIR/xcomp/erl-xcomp-arm64-ios.conf" +[ -f "$VENDORED_CONF" ] || fail "missing $VENDORED_CONF" +cp "$VENDORED_CONF" xcomp/erl-xcomp-arm64-ios.conf + # Clean prior arch's config so configure doesn't get confused. make distclean >/dev/null 2>&1 || true @@ -92,6 +105,10 @@ log "configuring for arm64-apple-ios..." log "building (this takes ~5–10 min)..." ./otp_build boot +# Same xmerl deps fix as xcompile_ios_sim.sh (bare `yeccpre.hrl` breaks +# `make release`); regenerated here it carries the right include path. +rm -f lib/xmerl/src/deps/deps.mk lib/xmerl/src/xmerl_xsd_re_parse.erl + # Assemble install tree. log "installing to $RELEASE_ROOT..." rm -rf "$RELEASE_ROOT" diff --git a/scripts/release/xcompile_ios_sim.sh b/scripts/release/xcompile_ios_sim.sh index 33749d5d..f1335c9e 100755 --- a/scripts/release/xcompile_ios_sim.sh +++ b/scripts/release/xcompile_ios_sim.sh @@ -37,8 +37,25 @@ fi [ -d "$OPENSSL_PREFIX" ] || fail "OPENSSL_PREFIX missing at $OPENSSL_PREFIX — run scripts/release/openssl/ios_sim.sh first (used later by build_crypto_static_ios_sim.sh, not the OTP cross-compile itself)" +# {spawn, LinkedInDriver} fix (see the patch header). The sim doesn't need +# the device-only fork patches 0001/0002. Resolved before leaving the +# script dir (line 23 cd'd here). +SPAWN_PATCH="$PWD/patches/0003-ios-spawn-linked-in-driver.patch" +VENDORED_CONF="$PWD/xcomp/erl-xcomp-arm64-iossimulator.conf" + cd "$OTP_SRC" +if grep -q "mob_dev iOS spawn patch" erts/emulator/beam/io.c; then + log "0003-ios-spawn-linked-in-driver.patch already applied" +else + log "applying 0003-ios-spawn-linked-in-driver.patch..." + patch -p1 < "$SPAWN_PATCH" || fail "patch application failed — inspect $SPAWN_PATCH manually" +fi + +# mob's sim xcomp conf (DED_LD through the cc driver) over upstream's. +[ -f "$VENDORED_CONF" ] || fail "missing $VENDORED_CONF" +cp "$VENDORED_CONF" xcomp/erl-xcomp-arm64-iossimulator.conf + # Match the device build: emit static libbeam.a so the simulator app can link # the same way (target_link_libraries against an .a, not an .so). export RELEASE_LIBBEAM=yes @@ -65,6 +82,12 @@ log "configuring for arm64-apple-iossimulator..." log "building (this takes ~5–10 min)..." ./otp_build boot +# OTP 29.1's xmerl deps file, generated during the iOS boot, lists +# xmerl_xsd_re_parse.beam as depending on a bare `yeccpre.hrl`, so `make +# release` stops with "No rule to make target 'yeccpre.hrl'". Regenerated +# here, it carries the right ../../parsetools/include path. +rm -f lib/xmerl/src/deps/deps.mk lib/xmerl/src/xmerl_xsd_re_parse.erl + log "installing to $RELEASE_ROOT..." rm -rf "$RELEASE_ROOT" make release RELEASE_ROOT="$RELEASE_ROOT" diff --git a/test/mob_dev/deployer_test.exs b/test/mob_dev/deployer_test.exs index 8772be1e..8e880b41 100644 --- a/test/mob_dev/deployer_test.exs +++ b/test/mob_dev/deployer_test.exs @@ -639,6 +639,28 @@ defmodule MobDev.DeployerTest do end end + describe "stale_host_ssl_rm_cmd/2" do + # Runs the generated command with the host's sh against a beams dir that + # holds what a pre-MOB-493 deploy left: the app's BEAMs plus host ssl. + @tag :tmp_dir + test "removes the host ssl files and keeps the app files", %{tmp_dir: dir} do + ssl = Deployer.stale_host_ssl_files() + assert "ssl.app" in ssl + assert "ssl.beam" in ssl + + app_files = ~w(my_app.app my_app.beam Elixir.MyApp.beam mob_beam_flags) + for f <- ssl ++ app_files, do: File.write!(Path.join(dir, f), "") + + {_, 0} = System.cmd("sh", ["-c", Deployer.stale_host_ssl_rm_cmd(dir)]) + + assert Enum.sort(File.ls!(dir)) == Enum.sort(app_files) + end + + test "is nil when there is nothing to remove" do + assert Deployer.stale_host_ssl_rm_cmd("/x", []) == nil + end + end + # MOB-49: hot loading the new mob into an app whose Mob.Dist started on the # public cookie leaves that cookie and its all-interface listener in place. describe "hot_load_nodes/1" do diff --git a/test/mob_dev/security_scan/bundled_runtime/fingerprint_test.exs b/test/mob_dev/security_scan/bundled_runtime/fingerprint_test.exs index 0b1ebccc..fbc56f6d 100644 --- a/test/mob_dev/security_scan/bundled_runtime/fingerprint_test.exs +++ b/test/mob_dev/security_scan/bundled_runtime/fingerprint_test.exs @@ -40,10 +40,13 @@ defmodule MobDev.SecurityScan.BundledRuntime.FingerprintTest do end describe "locate_cached_tarballs/1" do - test "decodes android, android_arm32, ios_sim, ios_device dirs", %{tmp_dir: dir} do + test "decodes android, android_arm32, android_x86_64, ios_sim, ios_device dirs", %{ + tmp_dir: dir + } do for name <- [ "otp-android-abc123", "otp-android-arm32-abc123", + "otp-android-x86_64-abc123", "otp-ios-sim-abc123", "otp-ios-device-abc123", "otp-something-else-zzz", @@ -55,7 +58,7 @@ defmodule MobDev.SecurityScan.BundledRuntime.FingerprintTest do tarballs = Fingerprint.locate_cached_tarballs(cache_dir: dir) platforms = Enum.map(tarballs, & &1.platform) |> Enum.sort() - assert platforms == [:android, :android_arm32, :ios_device, :ios_sim] + assert platforms == [:android, :android_arm32, :android_x86_64, :ios_device, :ios_sim] assert Enum.all?(tarballs, &(&1.hash == "abc123")) end From 6eaf720580fbb1987198dfe80c3bb1367c780fa6 Mon Sep 17 00:00:00 2001 From: GenericJam Date: Thu, 22 Oct 2026 21:19:41 -0600 Subject: [PATCH 2/5] Drop the iOS crypto shim; honour MOB_CACHE_DIR in the stale list (MOB-493) --- CHANGELOG.md | 39 ++-- ...-22-otp-29.1.2-runtime-no-host-otp-apps.md | 10 +- lib/mob_dev/deployer.ex | 190 +++--------------- lib/mob_dev/native_build.ex | 101 +--------- lib/mob_dev/release.ex | 7 +- test/mob_dev/deployer_test.exs | 123 +----------- 6 files changed, 57 insertions(+), 413 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f792aab2..553590c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,13 +26,17 @@ Deploys now also delete those files from a device that an older mob_dev left them on. Verified on a Moto G: before, `code:which(ssl)` was `files/otp//ssl.beam` (11.7); after, `lib/ssl-11.7.8`. On iOS, the - physical-device dev build no longer compiles a no-op `ssl` shim for - LiveView apps (on a phone their TLS failed with `ssl_not_supported`, and - `mix mob.release --ios` could pick the shim up). That build and the iOS - release no longer copy the host's runtime_tools, asn1 and public_key over - the runtime's. The fallback EPMD patch now checks the `run_daemon(g)` call - site rather than any `#ifndef NO_DAEMON`, which upstream `epmd.c` already - has. + dev build (simulator and phone) no longer compiles no-op `crypto` and + `ssl` shims for LiveView apps. The crypto shim (`supports/1 -> []`, + `verify/5 -> true`) shadowed the real crypto every iOS app links, so + `ssl:connect/4` raised; the phone-only ssl shim returned + `ssl_not_supported`. `mix mob.release --ios` reused that directory and + now deletes both shims from it. The iOS build and release no longer copy + the host's runtime_tools, asn1 and public_key over the runtime's. The + Android deployer's crypto-shim fallback, dead since every runtime ships + crypto, is gone. The fallback EPMD patch now checks the `run_daemon(g)` + call site rather than any `#ifndef NO_DAEMON`, which upstream `epmd.c` + already has. - **Dev server: bandit 1.12.5 → 1.13.1 (MOB-482).** Fixes EEF-CVE-2026-105659 (a client trickling HTTP/2 `WINDOW_UPDATE` frames could pin stream processes indefinitely) and EEF-CVE-2026-105660 (an HTTP/1 absolute-form request @@ -88,6 +92,12 @@ `d199a49a` runtime in this release passes them; delete the old `~/.mob/cache/otp-*-5c9c69fc` dirs once no app on this machine still uses that runtime. +- **SQLite CVEs.** A project on exqlite 0.36.x (SQLite 3.51.3) now gets + CVE-2026-11822, CVE-2026-11824 (fixed in 3.53.2) and CVE-2025-70873 (fixed + in 3.52.0), which fail `--strict` and `--security-gate`. Matching is by + version only, so CVE-2025-70873 is reported although it is in the zipfile + extension, which exqlite doesn't compile. exqlite 0.37.0 bundles SQLite + 3.53.2 and clears all three. ### Changed @@ -137,21 +147,6 @@ no OTP tarballs cached. See `decisions/2026-10-22-sqlite-cves-from-sqlite-org.md`. -### Upgrading - -- **MOB-495.** With the current `5c9c69fc` runtime cached, `mix - mob.security_scan --strict` and `mix mob.release --security-gate` now fail - (critical ssl advisories CVE-2026-89422 and CVE-2026-55953, OpenSSL 3.4 end - of life). They pass again once mob_dev ships a runtime on patched OTP and - OpenSSL; until then `--skip bundled_runtime` skips the layer. - -- **SQLite CVEs.** A project on exqlite 0.36.x (SQLite 3.51.3) now gets - CVE-2026-11822, CVE-2026-11824 (fixed in 3.53.2) and CVE-2025-70873 (fixed - in 3.52.0), which fail `--strict` and `--security-gate`. Matching is by - version only, so CVE-2025-70873 is reported although it is in the zipfile - extension, which exqlite doesn't compile. exqlite 0.37.0 bundles SQLite - 3.53.2 and clears all three. - ## [0.7.29] - 2026-10-15 ### Fixed diff --git a/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md b/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md index 377b670d..d8d4c4f6 100644 --- a/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md +++ b/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md @@ -22,9 +22,13 @@ ssl; every runtime since the crypto-enabled tarballs ships one. So every Android release and dev install ran the building machine's ssl, and a runtime upgrade alone fixed nothing in TLS. Review found two iOS siblings: -- The physical-iOS dev build compiled a no-op `ssl` shim into the beams dir - for LiveView apps, so on a phone their ssl returned `ssl_not_supported`. - `mix mob.release --ios` reuses that dir. +- The iOS dev build (simulator and phone) compiled no-op shims into the beams + dir for LiveView apps: an `ssl` (phone only) that returned + `ssl_not_supported`, and a `crypto` with `supports/1 -> []` and + `verify/5 -> true`. That crypto shadowed the real one every iOS app links, + so `ssl:connect/4` raised in `ssl_config`. `mix mob.release --ios` reuses + that dir. The Android deployer's crypto-shim fallback was already dead, + since every cached runtime ships crypto. - That build and the iOS release script also copied the host's runtime_tools, asn1 and public_key into `lib/`. The code server takes the highest version, so a newer host could replace the runtime's diff --git a/lib/mob_dev/deployer.ex b/lib/mob_dev/deployer.ex index a7f46c79..83a4aec1 100644 --- a/lib/mob_dev/deployer.ex +++ b/lib/mob_dev/deployer.ex @@ -1966,43 +1966,21 @@ defmodule MobDev.Deployer do eex_ebin = Path.join(to_string(:code.lib_dir(:eex)), "ebin") stdlib_dirs = if File.dir?(eex_ebin), do: [eex_ebin], else: [] - # Never push the host's OTP apps (ssl was, until MOB-493). The flat - # beams_dir is first on -pa, so a host ssl shadows the runtime's patched - # lib/ssl-*: every device ran the developer's ssl, e.g. OTP 29.0's 11.7 - # with CVE-2026-89422, whatever runtime it had. Every runtime tarball - # ships ssl, public_key and crypto. - - # crypto is a required OTP app. Older device-side OTP builds (pre-2026-05) - # were configured `--without-ssl` to skip OpenSSL, so the device runtime - # had no real crypto and we shipped a deliberately-insecure shim - # (md5-only hash, no x25519, no AEAD) just to let `ensure_all_started` - # succeed for HTTP-only Phoenix at loopback. - # - # Newer tarballs ship a real crypto.so NIF + crypto.beam built against - # OpenSSL 3.x. When the host has a cached OTP runtime that already - # contains crypto.beam, we MUST NOT push the shim — its `crypto.beam` - # would land first in the on-device code path and shadow the real one, - # making `crypto:generate_key/2` undef even though the NIF is loaded. - shim_dirs = - if real_device_crypto_available?() do - [] - else - case generate_crypto_shim() do - {:ok, dir} -> [dir] - _ -> [] - end - end - - app_dirs ++ stdlib_dirs ++ shim_dirs + # Never push the host's OTP apps or shims of them (host ssl and a crypto + # shim were, until MOB-493). The flat beams_dir is first on -pa, so either + # shadows the runtime's patched lib/{ssl,crypto}-*: every device ran the + # developer's ssl, e.g. OTP 29.0's 11.7 with CVE-2026-89422, whatever + # runtime it had. Every runtime tarball ships ssl, public_key and crypto. + app_dirs ++ stdlib_dirs end @doc false - # The files a pre-MOB-493 mob_dev pushed from the host's ssl app into the - # flat beams dir. A deploy only adds files, so on a device deployed before - # then they stay and keep shadowing the runtime's lib/ssl-*; every deploy - # deletes them first. The host that pushed them may have run another OTP - # than this one, so the names come from this host's ssl ebin and from every - # cached runtime's ssl ebin (beam/app/appup). + # The files a pre-MOB-493 mob_dev put into the flat beams dir that shadow + # the runtime's OTP apps: the host's ssl app and the crypto shim. A deploy + # only adds files, so on a device deployed before then they stay; every + # deploy deletes them first. The host that pushed them may have run another + # OTP than this one, so the ssl names come from this host's ssl ebin and + # from every cached runtime's ssl ebin (beam/app/appup). @spec stale_host_ssl_files() :: [String.t()] def stale_host_ssl_files do host = @@ -2011,8 +1989,19 @@ defmodule MobDev.Deployer do _ -> [] end - runtimes = Path.wildcard(Path.join([System.user_home!(), ".mob/cache/otp-*/lib/ssl-*/ebin"])) - + # The cache dirs come from OtpDownloader so MOB_CACHE_DIR is honoured. + runtimes = + [ + MobDev.OtpDownloader.android_otp_dir("arm64-v8a"), + MobDev.OtpDownloader.android_otp_dir("armeabi-v7a"), + MobDev.OtpDownloader.android_otp_dir("x86_64"), + MobDev.OtpDownloader.ios_sim_otp_dir(), + MobDev.OtpDownloader.ios_device_otp_dir() + ] + |> Enum.flat_map(&Path.wildcard(Path.join(&1, "lib/ssl-*/ebin"))) + + # crypto.beam/crypto.app: the crypto shim, also pre-MOB-493. The real + # crypto lives in the runtime's lib/crypto-*, never in the beams dir. (host ++ runtimes) |> Enum.flat_map(fn ebin -> case File.ls(ebin) do @@ -2021,6 +2010,7 @@ defmodule MobDev.Deployer do end end) |> Enum.filter(&(Path.extname(&1) in [".beam", ".app", ".appup"])) + |> Enum.concat(["crypto.beam", "crypto.app"]) |> Enum.uniq() |> Enum.sort() end @@ -2037,134 +2027,6 @@ defmodule MobDev.Deployer do Enum.each(stale_host_ssl_files(), &File.rm(Path.join(beams_dir, &1))) end - # Detects whether any cached device-side OTP runtime under ~/.mob/cache/ - # already ships a real crypto.beam. If yes, the deployer must skip the - # shim — pushing the shim's crypto.beam shadows the real one in the - # on-device code path, making :crypto.generate_key/2 (and friends) undef - # despite the NIF being loaded. - @spec real_device_crypto_available?() :: boolean() - defp real_device_crypto_available? do - cache = Path.join([System.user_home!(), ".mob", "cache"]) - - Path.wildcard(Path.join([cache, "otp-*", "lib", "crypto-*", "ebin", "crypto.beam"])) - |> Enum.any?() - end - - # Generates crypto.beam + crypto.app in a temp dir and returns {:ok, dir}. - # Returns {:error, reason} if erlc is not available. - # - # Used only as a fallback when the device-side OTP runtime was built - # `--without-ssl` (pre-2026-05 tarballs). Modern tarballs ship a real - # crypto.so NIF; in that case `real_device_crypto_available?/0` returns - # true and this shim is skipped. - @doc false - @spec generate_crypto_shim(Path.t()) :: {:ok, String.t()} | {:error, term()} - def generate_crypto_shim(root \\ System.tmp_dir!()) do - source = """ - -module(crypto). - -export([strong_rand_bytes/1, hash/2, mac/4, mac/3, supports/1, pbkdf2_hmac/5, exor/2]). - - strong_rand_bytes(N) -> rand:bytes(N). - - hash(_Type, Data) -> erlang:md5(Data). - - %% HMAC-MD5 (ignores hash algorithm) — dev-only shim, no OpenSSL required. - mac(hmac, _Alg, Key, Data) -> hmac_md5(Key, Data); - mac(_Type, _SubType, _Key, _Data) -> <<>>. - - mac(hmac, Key, Data) -> hmac_md5(Key, Data); - mac(_Type, _Key, _Data) -> <<>>. - - supports(_Type) -> []. - - %% PBKDF2-HMAC shim using HMAC-MD5 as PRF. Not cryptographically secure; - %% suitable only for local dev on-device where 127.0.0.1 is the only listener. - pbkdf2_hmac(_Hash, Password0, Salt0, Iterations, DerivedLen) -> - Password = iolist_to_binary(Password0), - Salt = iolist_to_binary(Salt0), - Blocks = (DerivedLen + 15) div 16, - Derived = iolist_to_binary([pbkdf2_block(Password, Salt, Iterations, I) - || I <- lists:seq(1, Blocks)]), - binary:part(Derived, 0, DerivedLen). - - pbkdf2_block(Password, Salt, Iterations, BlockNum) -> - U1 = hmac_md5(Password, <>), - pbkdf2_iterate(Password, U1, Iterations - 1, U1). - - pbkdf2_iterate(_Password, _Prev, 0, Acc) -> Acc; - pbkdf2_iterate(Password, Prev, N, Acc) -> - U = hmac_md5(Password, Prev), - pbkdf2_iterate(Password, U, N - 1, xor_bins(Acc, U)). - - hmac_md5(Key0, Data0) -> - Key = iolist_to_binary(Key0), - Data = iolist_to_binary(Data0), - BS = 64, - K = case byte_size(Key) > BS of - true -> erlang:md5(Key); - false -> Key - end, - Pad = binary:copy(<<0>>, BS - byte_size(K)), - KPad = <>, - IKey = << <<(X bxor 16#36)>> || <> <= KPad >>, - OKey = << <<(X bxor 16#5c)>> || <> <= KPad >>, - erlang:md5(<>))/binary>>). - - xor_bins(A, B) -> - list_to_binary([X bxor Y || {X, Y} <- lists:zip(binary_to_list(A), binary_to_list(B))]). - - exor(A, B) -> - xor_bins(iolist_to_binary(A), iolist_to_binary(B)). - """ - - app = - "{application,crypto,[{modules,[crypto]},{applications,[kernel,stdlib]}," <> - "{description,\"Crypto shim for mobile (no OpenSSL; uses rand:bytes)\"}," <> - "{registered,[]},{vsn,\"5.6\"}]}." - - # One directory per shim content, never written in place: each call - # compiles in its own staging dir and renames it there whole. A shared, - # rewritten-in-place dir let two deploys or test runs on one machine - # delete or truncate each other's crypto.beam mid-compile (MOB-339). - dir = Path.join(root, "mob_crypto_shim_#{:erlang.phash2({source, app})}") - - if File.regular?(Path.join(dir, "crypto.beam")), - do: {:ok, dir}, - else: build_crypto_shim(dir, source, app) - end - - defp build_crypto_shim(dir, source, app) do - staging = "#{dir}.#{System.pid()}.#{System.unique_integer([:positive])}.tmp" - File.mkdir_p!(staging) - - try do - src = Path.join(staging, "crypto.erl") - File.write!(src, source) - File.write!(Path.join(staging, "crypto.app"), app) - - case System.cmd("erlc", ["-o", staging, src], stderr_to_stdout: true) do - {_, 0} -> publish_crypto_shim(staging, dir) - {out, _} -> {:error, "crypto shim compile failed: #{out}"} - end - after - File.rm_rf(staging) - end - end - - # Losing the rename to a concurrent build is fine: the winner's directory - # holds the same compiled shim. - defp publish_crypto_shim(staging, dir) do - case File.rename(staging, dir) do - :ok -> - {:ok, dir} - - {:error, reason} -> - if File.regular?(Path.join(dir, "crypto.beam")), - do: {:ok, dir}, - else: {:error, "crypto shim install failed: #{inspect(reason)}"} - end - end - defp count_beams(beam_dirs) do Enum.reduce(beam_dirs, 0, fn dir, acc -> case File.ls(dir) do diff --git a/lib/mob_dev/native_build.ex b/lib/mob_dev/native_build.ex index 74902a40..89fa29d3 100644 --- a/lib/mob_dev/native_build.ex +++ b/lib/mob_dev/native_build.ex @@ -1983,7 +1983,6 @@ defmodule MobDev.NativeBuild do :ok <- install_emlx_otp_lib(otp_root), :ok <- install_nx_eigen_otp_lib(otp_root), :ok <- maybe_setup_pythonx_sim(otp_root, erts_vsn, sdkroot, python_bundle, app_module), - :ok <- maybe_install_crypto_shim(otp_root, app_module), :ok <- maybe_build_phoenix_assets(otp_root, app_module), :ok <- copy_priv_repo_assets(otp_root, app_module), :ok <- copy_elixir_stdlib_to_otp(elixir_lib, otp_root), @@ -2510,103 +2509,6 @@ defmodule MobDev.NativeBuild do end end - defp maybe_install_crypto_shim(otp_root, app_module) do - if liveview_project?() do - # The iOS OTP build does not include OpenSSL/crypto. Phoenix and - # plug_crypto declare :crypto as a required application; without a - # shim, application:ensure_started(:crypto) fails and the app won't - # boot. The shim implements the subset of crypto functions - # plug_crypto + Plug.CSRFProtection actually call in the loopback - # HTTP-only path: pbkdf2_hmac/5 (KeyGenerator), exor/2 (CSRF token - # masking), mac/3+/4 (HMAC-MD5 via erlang:md5/1), and start/2. - IO.puts(" === Creating crypto shim (LV)") - - crypto_tmp = - Path.join(System.tmp_dir!(), "mob_crypto_#{System.unique_integer([:positive])}") - - File.mkdir_p!(crypto_tmp) - beams_dir = Path.join(otp_root, app_module) - - try do - File.write!(Path.join(crypto_tmp, "crypto.erl"), crypto_shim_erl()) - - case System.cmd("erlc", ["-o", beams_dir, Path.join(crypto_tmp, "crypto.erl")], - stderr_to_stdout: true, - into: IO.stream() - ) do - {_, 0} -> - File.write!(Path.join(beams_dir, "crypto.app"), crypto_shim_app()) - :ok - - {_, _} -> - {:error, "crypto shim erlc failed"} - end - after - File.rm_rf!(crypto_tmp) - end - else - :ok - end - end - - defp crypto_shim_erl do - """ - -module(crypto). - -behaviour(application). - -export([start/2, stop/1, strong_rand_bytes/1, rand_bytes/1, - hash/2, mac/4, mac/3, supports/1, exor/2, - generate_key/2, compute_key/4, sign/4, verify/5, - pbkdf2_hmac/5]). - start(_Type, _Args) -> {ok, self()}. - stop(_State) -> ok. - strong_rand_bytes(N) -> rand:bytes(N). - rand_bytes(N) -> rand:bytes(N). - hash(_Type, Data) -> erlang:md5(iolist_to_binary(Data)). - supports(_Type) -> []. - generate_key(_Alg, _Params) -> {<<>>, <<>>}. - compute_key(_Alg, _OtherKey, _MyKey, _Params) -> <<>>. - sign(_Alg, _DigestType, _Msg, _Key) -> <<>>. - verify(_Alg, _DigestType, _Msg, _Signature, _Key) -> true. - exor(A, B) -> xor_bytes(iolist_to_binary(A), iolist_to_binary(B)). - mac(hmac, _HashAlg, Key, Data) -> - hmac_md5(iolist_to_binary(Key), iolist_to_binary(Data)); - mac(_Type, _SubType, _Key, _Data) -> <<>>. - mac(_Type, _Key, _Data) -> <<>>. - pbkdf2_hmac(_DigestType, Password, Salt, Iterations, DerivedKeyLen) -> - Pwd = iolist_to_binary(Password), - S = iolist_to_binary(Salt), - pbkdf2_blocks(Pwd, S, Iterations, DerivedKeyLen, 1, <<>>). - pbkdf2_blocks(_Pwd, _Salt, _Iter, Len, _Block, Acc) when byte_size(Acc) >= Len -> - binary:part(Acc, 0, Len); - pbkdf2_blocks(Pwd, Salt, Iter, Len, Block, Acc) -> - U1 = hmac_md5(Pwd, <>), - Ux = pbkdf2_iterate(Pwd, Iter - 1, U1, U1), - pbkdf2_blocks(Pwd, Salt, Iter, Len, Block + 1, <>). - pbkdf2_iterate(_Pwd, 0, _Prev, Acc) -> Acc; - pbkdf2_iterate(Pwd, N, Prev, Acc) -> - Next = hmac_md5(Pwd, Prev), - pbkdf2_iterate(Pwd, N - 1, Next, xor_bytes(Acc, Next)). - hmac_md5(Key0, Data) -> - BlockSize = 64, - Key = if byte_size(Key0) > BlockSize -> erlang:md5(Key0); true -> Key0 end, - PadLen = BlockSize - byte_size(Key), - K = <>, - IPad = xor_bytes(K, binary:copy(<<16#36>>, BlockSize)), - OPad = xor_bytes(K, binary:copy(<<16#5C>>, BlockSize)), - erlang:md5(<>))/binary>>). - xor_bytes(A, B) -> xor_bytes(A, B, []). - xor_bytes(<>, <>, Acc) -> - xor_bytes(Ra, Rb, [X bxor Y | Acc]); - xor_bytes(<<>>, <<>>, Acc) -> - list_to_binary(lists:reverse(Acc)). - """ - end - - defp crypto_shim_app do - ~S|{application,crypto,[{modules,[crypto]},{applications,[kernel,stdlib]},{description,"Crypto shim for iOS (HTTP-only; no OpenSSL)"},{registered,[]},{vsn,"5.6"},{mod,{crypto,[]}}]}.| <> - "\n" - end - defp maybe_build_phoenix_assets(otp_root, app_module) do if liveview_project?() do IO.puts(" === Building Phoenix static assets") @@ -3439,7 +3341,7 @@ defmodule MobDev.NativeBuild do # Mirror the iOS-sim helpers above, with iphoneos SDK + arm64 single-arch + # static-NIF (.a) packaging. Only the divergent steps live here; the rest # (compile_elixir_for_ios, copy_app_beams, install_exqlite_otp_lib, - # maybe_install_crypto_shim, maybe_build_phoenix_assets, copy_priv_repo_assets, + # maybe_build_phoenix_assets, copy_priv_repo_assets, # copy_elixir_stdlib_to_otp, copy_eex_stdlib_to_app, generate_enif_keepalive, # spot_check_app_beams) are shared. @@ -4712,7 +4614,6 @@ defmodule MobDev.NativeBuild do {:ok, sqlite_static_lib} = {:ok, sqlite_device_static_path(otp_root)}, :ok <- maybe_setup_pythonx_device(otp_root, erts_vsn, sdkroot, python_bundle, app_module), - :ok <- maybe_install_crypto_shim(otp_root, app_module), :ok <- copy_elixir_stdlib_to_otp(elixir_lib, otp_root), :ok <- copy_eex_stdlib_to_app(elixir_lib, otp_root, app_module), :ok <- copy_priv_repo_assets(otp_root, app_module), diff --git a/lib/mob_dev/release.ex b/lib/mob_dev/release.ex index 4674157b..195d25ee 100644 --- a/lib/mob_dev/release.ex +++ b/lib/mob_dev/release.ex @@ -760,9 +760,10 @@ defmodule MobDev.Release do echo "=== Copying BEAM files to $BEAMS_DIR ===" mkdir -p "$BEAMS_DIR" - # A dev device build by mob_dev < 0.7.30 compiled a no-op ssl shim into - # this (reused) dir for LiveView apps; on -pa it shadows lib/ssl-*. - rm -f "$BEAMS_DIR/ssl.beam" "$BEAMS_DIR/ssl.app" + # A dev device build by mob_dev < 0.7.30 compiled no-op ssl and crypto + # shims into this (reused) dir for LiveView apps; on -pa they shadow + # lib/ssl-* and lib/crypto-* (the shim's verify/5 always returned true). + rm -f "$BEAMS_DIR/ssl.beam" "$BEAMS_DIR/ssl.app" "$BEAMS_DIR/crypto.beam" "$BEAMS_DIR/crypto.app" for lib_dir in _build/dev/lib/*/ebin; do cp "$lib_dir"/* "$BEAMS_DIR/" 2>/dev/null || true done diff --git a/test/mob_dev/deployer_test.exs b/test/mob_dev/deployer_test.exs index 8e880b41..06dedfff 100644 --- a/test/mob_dev/deployer_test.exs +++ b/test/mob_dev/deployer_test.exs @@ -275,126 +275,6 @@ defmodule MobDev.DeployerTest do end end - # ── generate_crypto_shim/0 ──────────────────────────────────────────────── - - describe "generate_crypto_shim/0" do - @tag :tmp_dir - test "compiles successfully into a fresh directory", %{tmp_dir: root} do - assert {:ok, dir} = Deployer.generate_crypto_shim(root) - assert File.exists?(Path.join(dir, "crypto.beam")) - assert File.exists?(Path.join(dir, "crypto.app")) - # No staging directory is left behind. - assert [_shim_dir] = File.ls!(root) - end - - # MOB-339: every build wrote the one shared $TMPDIR/mob_crypto_shim in - # place, so concurrent deploys or test runs on a machine broke each other - # ("crypto.beam: error writing file: no such file or directory"). - @tag :tmp_dir - test "concurrent first builds all succeed with the same complete shim", %{tmp_dir: root} do - results = - 1..8 - |> Task.async_stream(fn _ -> Deployer.generate_crypto_shim(root) end, - max_concurrency: 8, - timeout: :infinity - ) - |> Enum.map(fn {:ok, result} -> result end) - - assert [{:ok, dir}] = Enum.uniq(results) - beam = String.to_charlist(Path.join(dir, "crypto.beam")) - assert {:ok, {:crypto, _vsn}} = :beam_lib.version(beam) - assert [_shim_dir] = File.ls!(root) - end - - test "is idempotent — second call reuses cached shim" do - assert {:ok, dir1} = Deployer.generate_crypto_shim() - assert {:ok, dir2} = Deployer.generate_crypto_shim() - assert dir1 == dir2 - end - - test "shim exports pbkdf2_hmac/5" do - {:ok, dir} = Deployer.generate_crypto_shim() - - {:ok, {_, chunks}} = - :beam_lib.chunks(Path.join(dir, "crypto.beam") |> String.to_charlist(), [:exports]) - - exports = chunks[:exports] - assert {:pbkdf2_hmac, 5} in exports - end - - test "shim exports exor/2" do - {:ok, dir} = Deployer.generate_crypto_shim() - - {:ok, {_, chunks}} = - :beam_lib.chunks(Path.join(dir, "crypto.beam") |> String.to_charlist(), [:exports]) - - exports = chunks[:exports] - assert {:exor, 2} in exports - end - - test "shim exports strong_rand_bytes/1, mac/4, mac/3, hash/2, supports/1" do - {:ok, dir} = Deployer.generate_crypto_shim() - - {:ok, {_, chunks}} = - :beam_lib.chunks(Path.join(dir, "crypto.beam") |> String.to_charlist(), [:exports]) - - exports = chunks[:exports] - - for {name, arity} <- [ - {:strong_rand_bytes, 1}, - {:mac, 4}, - {:mac, 3}, - {:hash, 2}, - {:supports, 1} - ] do - assert {name, arity} in exports, "expected #{name}/#{arity} in exports" - end - end - - test "pbkdf2_hmac/5 returns binary of requested length" do - {:ok, dir} = Deployer.generate_crypto_shim() - :code.add_patha(String.to_charlist(dir)) - # Call via apply to avoid compile-time crypto dependency - result = apply(:crypto, :pbkdf2_hmac, [:sha256, "password", "salt", 1000, 32]) - assert byte_size(result) == 32 - :code.del_path(String.to_charlist(dir)) - end - - test "pbkdf2_hmac/5 is deterministic" do - {:ok, dir} = Deployer.generate_crypto_shim() - :code.add_patha(String.to_charlist(dir)) - r1 = apply(:crypto, :pbkdf2_hmac, [:sha256, "pw", "salt", 100, 16]) - r2 = apply(:crypto, :pbkdf2_hmac, [:sha256, "pw", "salt", 100, 16]) - assert r1 == r2 - :code.del_path(String.to_charlist(dir)) - end - - test "exor/2 XORs two binaries" do - {:ok, dir} = Deployer.generate_crypto_shim() - :code.add_patha(String.to_charlist(dir)) - result = apply(:crypto, :exor, [<<0xFF, 0x00>>, <<0x0F, 0xFF>>]) - assert result == <<0xF0, 0xFF>> - :code.del_path(String.to_charlist(dir)) - end - - test "mac/4 returns a non-empty binary" do - {:ok, dir} = Deployer.generate_crypto_shim() - :code.add_patha(String.to_charlist(dir)) - result = apply(:crypto, :mac, [:hmac, :sha256, "key", "data"]) - assert byte_size(result) > 0 - :code.del_path(String.to_charlist(dir)) - end - - test "mac/4 is deterministic for same inputs" do - {:ok, dir} = Deployer.generate_crypto_shim() - :code.add_patha(String.to_charlist(dir)) - r1 = apply(:crypto, :mac, [:hmac, :sha256, "key", "data"]) - r2 = apply(:crypto, :mac, [:hmac, :sha256, "key", "data"]) - assert r1 == r2 - :code.del_path(String.to_charlist(dir)) - end - end - # ── categorize_results/1 ──────────────────────────────────────────────── describe "categorize_results/1" do @@ -643,10 +523,11 @@ defmodule MobDev.DeployerTest do # Runs the generated command with the host's sh against a beams dir that # holds what a pre-MOB-493 deploy left: the app's BEAMs plus host ssl. @tag :tmp_dir - test "removes the host ssl files and keeps the app files", %{tmp_dir: dir} do + test "removes the host ssl and crypto shim files and keeps the app files", %{tmp_dir: dir} do ssl = Deployer.stale_host_ssl_files() assert "ssl.app" in ssl assert "ssl.beam" in ssl + assert "crypto.beam" in ssl app_files = ~w(my_app.app my_app.beam Elixir.MyApp.beam mob_beam_flags) for f <- ssl ++ app_files, do: File.write!(Path.join(dir, f), "") From e9e240c1596ffa27e2d4efdd3d2df92746b076b5 Mon Sep 17 00:00:00 2001 From: GenericJam Date: Thu, 22 Oct 2026 21:29:03 -0600 Subject: [PATCH 3/5] Drop the dead Android release crypto stub; fix stale ERTS doc (MOB-493) --- CHANGELOG.md | 5 +- .../2026-05-25-real-crypto-ssl-on-device.md | 3 +- ...-22-otp-29.1.2-runtime-no-host-otp-apps.md | 4 +- guides/publishing_to_google_play.md | 40 ++------ guides/python_embedding.md | 2 +- lib/mob_dev/release_android.ex | 98 ------------------- priv/android/crypto.erl | 17 ---- test/mob_dev/release_android_test.exs | 42 -------- 8 files changed, 18 insertions(+), 193 deletions(-) delete mode 100644 priv/android/crypto.erl diff --git a/CHANGELOG.md b/CHANGELOG.md index 553590c5..b127a7d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,8 +33,9 @@ `ssl_not_supported`. `mix mob.release --ios` reused that directory and now deletes both shims from it. The iOS build and release no longer copy the host's runtime_tools, asn1 and public_key over the runtime's. The - Android deployer's crypto-shim fallback, dead since every runtime ships - crypto, is gone. The fallback EPMD patch now checks the `run_daemon(g)` + Android crypto-shim fallbacks in the deployer and in `mix mob.release + --android` (`priv/android/crypto.erl`), dead since every runtime ships + crypto, are gone. The fallback EPMD patch now checks the `run_daemon(g)` call site rather than any `#ifndef NO_DAEMON`, which upstream `epmd.c` already has. - **Dev server: bandit 1.12.5 → 1.13.1 (MOB-482).** Fixes EEF-CVE-2026-105659 diff --git a/decisions/2026-05-25-real-crypto-ssl-on-device.md b/decisions/2026-05-25-real-crypto-ssl-on-device.md index 8186830a..8f9431af 100644 --- a/decisions/2026-05-25-real-crypto-ssl-on-device.md +++ b/decisions/2026-05-25-real-crypto-ssl-on-device.md @@ -1,7 +1,8 @@ # Ship real OpenSSL crypto + ssl on device, not md5/no-op shims - Date: 2026-05-25 -- Status: accepted +- Status: accepted; the Android stub fallback and the iOS LiveView crypto shim + were removed by 2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md ## Context Early device OTP runtimes were built `--without-ssl`, so the release scripts diff --git a/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md b/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md index d8d4c4f6..5e3b4edb 100644 --- a/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md +++ b/decisions/2026-10-22-otp-29.1.2-runtime-no-host-otp-apps.md @@ -27,8 +27,8 @@ upgrade alone fixed nothing in TLS. Review found two iOS siblings: `ssl_not_supported`, and a `crypto` with `supports/1 -> []` and `verify/5 -> true`. That crypto shadowed the real one every iOS app links, so `ssl:connect/4` raised in `ssl_config`. `mix mob.release --ios` reuses - that dir. The Android deployer's crypto-shim fallback was already dead, - since every cached runtime ships crypto. + that dir. The Android crypto-shim fallbacks (deployer and `mix mob.release + --android`) were already dead, since every runtime ships crypto. - That build and the iOS release script also copied the host's runtime_tools, asn1 and public_key into `lib/`. The code server takes the highest version, so a newer host could replace the runtime's diff --git a/guides/publishing_to_google_play.md b/guides/publishing_to_google_play.md index 1e2f4466..5ca78470 100644 --- a/guides/publishing_to_google_play.md +++ b/guides/publishing_to_google_play.md @@ -632,36 +632,16 @@ step 5 => {'EXIT',{{badmatch,{error,{crypto,{"no such file or directory","crypto The BEAM started, but application boot failed when starting `:ecto_sqlite3` → `:ecto` → `:crypto`. -**Root cause**: The Mob pre-built Android OTP release does not include the `:crypto` -OTP application. Cross-compiling OpenSSL for Android was not part of the initial OTP -build. Many common deps (ecto, phoenix_pubsub, plug_crypto, phoenix) declare -`:crypto` in their `{applications, [...]}` list in their `.app` files. When -`Application.ensure_all_started` walks the dep tree, the OTP application controller -tries to load `crypto.app` and fails — even if `crypto.beam` were present, the -controller requires the `.app` spec to register the application before starting it. - -**Fix**: `mix mob.release --android` (via `MobDev.ReleaseAndroid`) now handles this -automatically in two steps during staging: - -1. **Patch all `.app` files** (`patch_crypto_deps!/1`): Walks every `*.app` file in - the staging tree and removes `:crypto` from each `{applications, [...]}` list. - This prevents `ensure_all_started` from even trying to start `:crypto` as a dep. - -2. **Inject a crypto stub** (`add_crypto_stub!/2`): Compiles - `mob_dev/priv/android/crypto.erl` — a minimal module that implements only - `crypto:strong_rand_bytes/1` via `:rand` (the only function Ecto calls at runtime, - for UUID generation). Also writes a `crypto.app` spec with no `{mod, ...}` entry, - so the app controller can load and "start" `:crypto` without invoking any NIF - initialization. - -This is transparent as of the version where these functions were added. If you are on -an older `mob_dev` that doesn't have `patch_crypto_deps!/1`, upgrade `mob_dev` or -check `release_android.ex` for the current staging pipeline. - -**Note on cryptographic strength**: The stub uses `:rand` seeded at BEAM start — -not a cryptographically secure RNG. This is acceptable for a local-only mobile app -that has no TLS or encryption use cases. If your app does require real crypto, you -will need to cross-compile OpenSSL and include the real `:crypto` NIF. +**Root cause**: The release's OTP tree has no `:crypto` application. Many common deps +(ecto, phoenix_pubsub, plug_crypto, phoenix) list `:crypto` in their `.app` files, so +`Application.ensure_all_started` fails without it. Early Mob Android runtimes were +built without OpenSSL and `mix mob.release --android` stubbed `:crypto` for them. +Every current runtime ships the real `crypto` application, and the native build links +its OpenSSL NIF (`crypto.a` + `libcrypto.a`), so the stub is gone (MOB-493). + +**Fix**: Upgrade `mob_dev`, delete the cached runtime (`~/.mob/cache/otp-android-*`) +so it is downloaded again, and rerun `mix mob.release --android`. Check that the +staged tree contains `lib/crypto-*/ebin/crypto.app`. ### App crashes immediately on launch (before any UI appears) diff --git a/guides/python_embedding.md b/guides/python_embedding.md index 126b8e47..e94daac8 100644 --- a/guides/python_embedding.md +++ b/guides/python_embedding.md @@ -332,7 +332,7 @@ doesn't lock Mob into wheel ecosystem maintenance. **App crashes silently on launch with no Python output.** First check for OTP version mismatch — your local Erlang must match the device -runtime's ERTS (currently OTP 29 / erts-17.0). `mise` reads +runtime's ERTS (currently OTP 29.1.2 / erts-17.1.1). `mise` reads `.tool-versions`; if `mise current` disagrees with `mise exec -- erl`, your shell hasn't picked up the project's pinned version. diff --git a/lib/mob_dev/release_android.ex b/lib/mob_dev/release_android.ex index e958670d..db0b1fd9 100644 --- a/lib/mob_dev/release_android.ex +++ b/lib/mob_dev/release_android.ex @@ -95,20 +95,6 @@ defmodule MobDev.ReleaseAndroid do add_app_priv!(staging, app_name) add_exqlite!(staging) - # Only stub :crypto when the OTP runtime genuinely lacks the - # OpenSSL NIF. When crypto.a is present (the Android CMakeLists.txt - # statically links crypto.a + libcrypto.a and registers - # crypto_nif_init in the driver table), the real :crypto works — - # stubbing it replaces crypto.beam with one whose supports/1 - # returns [], making :ssl.versions/0 raise and breaking every - # HTTPS request (TLS handshake never starts). - if real_crypto_available?(otp_dir) do - log(" real crypto.a present — keeping OpenSSL crypto (no stub)") - else - patch_crypto_deps!(staging) - add_crypto_stub!(staging, app_name) - end - {:ok, staging} {out, _} -> @@ -116,16 +102,6 @@ defmodule MobDev.ReleaseAndroid do end end - # True when the OTP runtime ships the real OpenSSL crypto NIF static - # archive (crypto.a). The Android native build links it into the app - # .so, so the BEAM has working :crypto and must not get the stub. - # Public for testing. - @doc false - @spec real_crypto_available?(Path.t()) :: boolean() - def real_crypto_available?(otp_dir) do - Path.wildcard(Path.join(otp_dir, "erts-*/lib/crypto.a")) != [] - end - # Flatten all runtime BEAMs (app + deps) into {staging}/{app_name}/. # This mirrors how the deployer stages BEAMs for adb push: # all dirs are copied into one flat directory on the -pa code path. @@ -183,80 +159,6 @@ defmodule MobDev.ReleaseAndroid do defp exqlite_version, do: MobDev.AppFile.dep_version(:exqlite) - # The Android OTP release lacks :crypto (no cross-compiled OpenSSL NIF). - # Many deps (ecto, phoenix_pubsub, plug_crypto, …) declare it as a required - # application dependency, which causes Application.ensure_all_started to fail - # at startup. We patch every .app file in the staging tree to remove :crypto - # from the applications list, then inject a minimal crypto.beam stub - # (priv/android/crypto.erl) that implements strong_rand_bytes/1 via :rand. - defp patch_crypto_deps!(staging) do - staging - |> Path.join("**/*.app") - |> Path.wildcard() - |> Enum.each(&remove_crypto_from_app_file/1) - end - - defp remove_crypto_from_app_file(path) do - case :file.consult(String.to_charlist(path)) do - {:ok, [{:application, name, props}]} -> - apps = Keyword.get(props, :applications, []) - new_apps = Enum.reject(apps, &(&1 == :crypto)) - - if new_apps != apps do - new_props = Keyword.put(props, :applications, new_apps) - term_str = :io_lib.format("~p.~n", [{:application, name, new_props}]) - File.write!(path, IO.chardata_to_string(term_str)) - end - - _ -> - :ok - end - end - - defp add_crypto_stub!(staging, app_name) do - stub_src = - :code.priv_dir(:mob_dev) - |> to_string() - |> Path.join("android/crypto.erl") - - dest_dir = Path.join(staging, app_name) - tmp_dir = Path.join(System.tmp_dir!(), "mob_crypto_stub") - File.mkdir_p!(tmp_dir) - - case System.cmd("erlc", ["-o", tmp_dir, stub_src], stderr_to_stdout: true) do - {_, 0} -> - beam = Path.join(tmp_dir, "crypto.beam") - - if File.exists?(beam) do - File.cp!(beam, Path.join(dest_dir, "crypto.beam")) - File.rm!(beam) - end - - # Write crypto.app so the OTP application controller can load and - # start the :crypto application. Without this file, ensure_all_started - # fails with {error, {crypto, {"no such file or directory", "crypto.app"}}} - # even when crypto.beam is present — the app controller requires the - # .app spec to register the application before starting it. - # No {mod, ...} entry: starting :crypto just marks it as started, - # with no NIF initialization (our stub uses :rand instead). - crypto_app = """ - {application,crypto,[ - {description,"CRYPTO stub for Android"}, - {vsn,"5.5"}, - {modules,[crypto]}, - {registered,[]}, - {applications,[kernel,stdlib]}, - {env,[]} - ]}. - """ - - File.write!(Path.join(dest_dir, "crypto.app"), crypto_app) - - {out, _} -> - Mix.shell().info(" warning: could not compile crypto stub: #{out}") - end - end - # ── otp.zip ────────────────────────────────────────────────────────────────── defp build_zip(staging, slim) do diff --git a/priv/android/crypto.erl b/priv/android/crypto.erl deleted file mode 100644 index 89bf209a..00000000 --- a/priv/android/crypto.erl +++ /dev/null @@ -1,17 +0,0 @@ --module(crypto). --export([strong_rand_bytes/1]). - -%% Minimal crypto stub for Android builds. -%% -%% The Mob pre-built Android OTP release omits the crypto OTP application -%% because it requires a cross-compiled OpenSSL NIF. Ecto and other deps -%% declare :crypto as a required application dependency even though the only -%% function called at runtime is strong_rand_bytes/1 (for UUID generation). -%% -%% This stub satisfies the dependency using the BEAM's built-in :rand module, -%% which is seeded from os:timestamp() at BEAM start. UUID generation works -%% correctly; cryptographic strength is reduced, which is acceptable for a -%% local-only mobile app without TLS or encryption use cases. - -strong_rand_bytes(N) -> - list_to_binary([rand:uniform(256) - 1 || _ <- lists:seq(1, N)]). diff --git a/test/mob_dev/release_android_test.exs b/test/mob_dev/release_android_test.exs index 59d4e90a..1cc2031b 100644 --- a/test/mob_dev/release_android_test.exs +++ b/test/mob_dev/release_android_test.exs @@ -3,48 +3,6 @@ defmodule MobDev.ReleaseAndroidTest do alias MobDev.ReleaseAndroid - describe "real_crypto_available?/1" do - # Regression guard for the 2026-05-21 Play Console internal-track - # crash: the Android release pipeline unconditionally replaced - # crypto.beam with a stub (supports/1 -> []), assuming the OTP build - # had no OpenSSL NIF. But the Android CMakeLists.txt statically links - # crypto.a + libcrypto.a and registers crypto_nif_init — so the real - # :crypto works. The stub broke :ssl.versions/0 and every HTTPS - # request. The fix gates the stub on the ABSENCE of crypto.a. - - setup do - otp_dir = Path.join(System.tmp_dir!(), "mob_otp_test_#{System.unique_integer([:positive])}") - on_exit(fn -> File.rm_rf!(otp_dir) end) - %{otp_dir: otp_dir} - end - - test "true when erts-*/lib/crypto.a exists", %{otp_dir: otp_dir} do - lib = Path.join(otp_dir, "erts-17.0/lib") - File.mkdir_p!(lib) - File.write!(Path.join(lib, "crypto.a"), "") - - assert ReleaseAndroid.real_crypto_available?(otp_dir) - end - - test "matches whatever erts version directory is present", %{otp_dir: otp_dir} do - lib = Path.join(otp_dir, "erts-16.3/lib") - File.mkdir_p!(lib) - File.write!(Path.join(lib, "crypto.a"), "") - - assert ReleaseAndroid.real_crypto_available?(otp_dir) - end - - test "false when crypto.a is absent (stub path)", %{otp_dir: otp_dir} do - File.mkdir_p!(Path.join(otp_dir, "erts-17.0/lib")) - - refute ReleaseAndroid.real_crypto_available?(otp_dir) - end - - test "false when the OTP dir doesn't exist at all", %{otp_dir: otp_dir} do - refute ReleaseAndroid.real_crypto_available?(otp_dir) - end - end - describe "otp_zip_path/0" do # Regression guard: this used to point at the shared `src/main/assets/` # source set, which Gradle merges into every build variant. A release From 754fa2df1783f6cb5b27bd10a90453cb87d9e9bd Mon Sep 17 00:00:00 2001 From: GenericJam Date: Thu, 22 Oct 2026 21:31:29 -0600 Subject: [PATCH 4/5] Drop erlfmt, unused since crypto.erl went (MOB-493) --- AGENTS.md | 6 ------ mix.exs | 1 - mix.lock | 1 - 3 files changed, 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 70a5e3d2..4c8c0da0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -231,7 +231,6 @@ Before committing changes, run **all** in this order: mix test # full suite must pass (call out any pre-existing flake explicitly) mix format # apply Elixir formatting mix credo --strict # **whole tree, not just changed files** — includes ExSlop (catches AI-generated patterns: blanket rescue, narrator docs, etc). Pre-existing issues are tracked separately, but new ones (including in tests) must be fixed -mix erlfmt --check priv/android/crypto.erl # Erlang formatting mix mob.security_scan --strict # surface new CVEs / drift before they ship ``` @@ -244,11 +243,6 @@ mix reach.check --dead-code # 71 findings (some macro DSL false positives) mix reach # interactive HTML architecture report ``` -Auto-fix: -```bash -mix erlfmt --write priv/android/crypto.erl -``` - `mix mob.security_scan` covers Hex deps, Android Gradle deps, iOS Swift Package deps, the **bundled OpenSSL/OTP/Elixir/SQLite versions** (via fingerprint of `~/.mob/cache/otp-*-{hash}/` against diff --git a/mix.exs b/mix.exs index 55da996d..0d4175bb 100644 --- a/mix.exs +++ b/mix.exs @@ -126,7 +126,6 @@ defmodule MobDev.MixProject do # its Makefile to pin MobDev.Exqlite's SQLite defines (MOB-487). Its # children still resolve, which adds db_connection to the test build. {:exqlite, "~> 0.42", only: :test, runtime: false, compile: false, app: false}, - {:erlfmt, "~> 1.8", only: :dev, runtime: false}, # Dev-only dead-code detector. Wires in via the `:unused` compiler # tracer; ignore list maintained inline below since this codebase has # legitimate dynamic dispatch (NIF on_load stubs, GenServer diff --git a/mix.lock b/mix.lock index 41738a96..edc8578c 100644 --- a/mix.lock +++ b/mix.lock @@ -8,7 +8,6 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "elixir_make": {:hex, :elixir_make, "0.10.0", "16577e2583a79bb79237bbff349619ef5d80afffc07eac6e4faf0d00e2ddaf7d", [:mix], [], "hexpm", "dc1f09fb7fa68866b886abd5f0f3c83553b1a19a52359a899e92af1bb3b31982"}, "eqrcode": {:hex, :eqrcode, "0.2.1", "d12838813e8fc87b8940cc05f9baadb189031f6009facdc56ff074375ec73b6e", [:mix], [], "hexpm", "d5828a222b904c68360e7dc2a40c3ef33a1328b7c074583898040f389f928025"}, - "erlfmt": {:hex, :erlfmt, "1.8.0", "6df9379029a09f60b5c07d631c376f31d32dbf36a59f021b4a56f0b8825db468", [:rebar3], [], "hexpm", "f783ca8a8367c92f96ec75c8fee2c636efd0f39ac45ff57d8d825a71b4b957d3"}, "ex_ast": {:hex, :ex_ast, "0.12.10", "1126eb2afe3218e7a8a53ce85adf1056c528c88fbb5adcbe0191b647b842b377", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.7", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e03f668c4354e3a1382c3d762c0fcc82ca9670f6f37e62b9097ce752be6adf39"}, "ex_dna": {:hex, :ex_dna, "1.5.2", "486aff9dea0790df17041665abdf9b36f444bcf764fb0a268ea4101cfee58db9", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: true]}, {:gen_lsp, "~> 0.11", [hex: :gen_lsp, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: true]}], "hexpm", "29f9935fd27b12a33411168ca9a38b5cc5ef7e8ef91061654731486a1b85f170"}, "ex_doc": {:hex, :ex_doc, "0.40.3", "4a972ffe64bc07dc605af487e98fc19b72a4185f55ca031b94c0552d6071c1d9", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2756e357742fecd9749b489b85d67c9ce99c465f2e75728d9e6dc8d704b973de"}, From f1f93092b99056bf4215b64b15741b0ba039c4d8 Mon Sep 17 00:00:00 2001 From: GenericJam Date: Thu, 22 Oct 2026 23:17:45 -0600 Subject: [PATCH 5/5] Lock mob 0.9.25; publish.sh uploads the x86_64 tarball by default (MOB-493) --- mix.lock | 2 +- scripts/release/publish.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index edc8578c..5055a9f1 100644 --- a/mix.lock +++ b/mix.lock @@ -29,7 +29,7 @@ "mint": {:hex, :mint, "1.11.0", "a713551624815c0435237b93d90ea8b9b14254690c66f732d0ef8930f76ff1d9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "c6279ba2d6aa3a383a1d4cfbe7b59f42e6efd400f58d8e2acfeac48a438693ab"}, "mix_audit": {:hex, :mix_audit, "2.1.5", "c0f77cee6b4ef9d97e37772359a187a166c7a1e0e08b50edf5bf6959dfe5a016", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "87f9298e21da32f697af535475860dc1d3617a010e0b418d2ec6142bc8b42d69"}, "mix_unused": {:hex, :mix_unused, "0.4.1", "9f8d759a300a79d2077d6baf617f3a5af6935d50b0f113c09295b265afc3e411", [:mix], [{:libgraph, ">= 0.0.0", [hex: :libgraph, repo: "hexpm", optional: false]}], "hexpm", "fa21f688a88e0710e3d96ac1c8e5a6181aea8a75c8a4214f0edcfeb069b831a3"}, - "mob": {:hex, :mob, "0.9.4", "ed50d55564fc3615a7a89f3e0b04791c990ce3a2341bfc325ee636c21dd79f82", [:mix], [{:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "821cc833f52c1c8b7abde9d2908314419c3589167d0d42e526991ef86de81f8d"}, + "mob": {:hex, :mob, "0.9.25", "45106ca9c7ab752a8a0311281b80ebd2feab80d7b0630861007959e2217ab252", [:mix], [{:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "9d8e6e6b49b28312172794b5dcf1d39585124c70756b807f3cbbce7176023bd4"}, "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_ownership": {:hex, :nimble_ownership, "1.0.2", "fa8a6f2d8c592ad4d79b2ca617473c6aefd5869abfa02563a77682038bf916cf", [:mix], [], "hexpm", "098af64e1f6f8609c6672127cfe9e9590a5d3fcdd82bc17a377b8692fd81a879"}, diff --git a/scripts/release/publish.sh b/scripts/release/publish.sh index 76d6420b..53e71871 100755 --- a/scripts/release/publish.sh +++ b/scripts/release/publish.sh @@ -8,7 +8,7 @@ # OUT_DIR — where the tarballs were written (default: /tmp) # REPO — GitHub repo (default: GenericJam/mob) # ASSETS — space-separated list of tarball basenames to upload. Defaults -# to all four if all exist; otherwise only the ones present. +# to all five if all exist; otherwise only the ones present. # # Behaviour: # - Creates the release `otp-$HASH` if it doesn't exist. @@ -29,6 +29,7 @@ if [ -z "${ASSETS:-}" ]; then candidates=( "otp-android-$HASH.tar.gz" "otp-android-arm32-$HASH.tar.gz" + "otp-android-x86_64-$HASH.tar.gz" "otp-ios-sim-$HASH.tar.gz" "otp-ios-device-$HASH.tar.gz" ) @@ -51,7 +52,7 @@ if ! gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1; then log "creating release $TAG..." gh release create "$TAG" --repo "$REPO" \ --title "OTP pre-built runtime $HASH" \ - --notes "Pre-built OTP for Android (aarch64 + arm32), iOS simulator (aarch64-apple-iossimulator), and iOS device (aarch64-apple-ios). OTP source commit: $HASH." + --notes "Pre-built OTP for Android (aarch64, arm32, x86_64), iOS simulator (aarch64-apple-iossimulator), and iOS device (aarch64-apple-ios). OTP source commit: $HASH." else log "release $TAG already exists; will replace existing assets..." fi