Skip to content

Commit d7528c8

Browse files
ericmjmaennchen
andcommitted
Fix checksum verification for dependencies in mix.lock
Checksum verification was not being performed due to a type mismatch in pattern matching. Comparing atom-based names against string-based lock data caused the verification to be silently skipped. Fixes: GHSA-hmv9-4mfr-m92v Fixes: CVE-2026-32148 Co-authored-by: Jonatan Männchen <jonatan@maennchen.ch> Co-authored-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
1 parent b5a1e94 commit d7528c8

2 files changed

Lines changed: 54 additions & 4 deletions

File tree

lib/hex/remote_converger.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -569,10 +569,8 @@ defmodule Hex.RemoteConverger do
569569

570570
defp verify_resolved(resolved, lock) do
571571
Enum.each(resolved, fn {repo, name, app, version} ->
572-
atom_name = String.to_atom(name)
573-
574572
case Hex.Utils.lock(lock[String.to_atom(app)]) do
575-
%{name: ^atom_name, version: ^version, repo: ^repo} = lock ->
573+
%{name: ^name, version: ^version, repo: ^repo} = lock ->
576574
verify_inner_checksum(repo, name, version, lock.inner_checksum)
577575
verify_outer_checksum(repo, name, version, lock.outer_checksum)
578576
verify_deps(repo, name, version, lock.deps)
@@ -599,14 +597,16 @@ defmodule Hex.RemoteConverger do
599597
end
600598
end
601599

600+
defp verify_deps(repo, name, version, deps)
602601
defp verify_deps(nil, _name, _version, _deps), do: :ok
602+
defp verify_deps(_repo, _name, _version, nil), do: []
603603

604604
defp verify_deps(repo, name, version, deps) do
605605
# TODO: Use requests?
606606
deps =
607607
Enum.map(deps, fn {app, req, opts} ->
608608
%{
609-
repo: opts[:repo],
609+
repo: if(opts[:repo] != "hexpm", do: opts[:repo]),
610610
name: opts[:hex],
611611
constraint: Hex.Solver.parse_constraint!(req),
612612
optional: !!opts[:optional],

test/hex/remote_converger_test.exs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,54 @@ defmodule Hex.RemoteConvergerTest do
198198
refute_received {:mix_shell, :yes?, _}
199199
end)
200200
end
201+
202+
defmodule ChecksumIntegrity.MixProject do
203+
def project do
204+
[
205+
app: :checksum_integrity,
206+
version: "0.1.0",
207+
deps: [
208+
{:ex_doc, "~> 0.1.0"}
209+
]
210+
]
211+
end
212+
end
213+
214+
test "raises on checksum mismatch in mix.lock" do
215+
in_tmp(fn ->
216+
Mix.Project.push(ChecksumIntegrity.MixProject)
217+
218+
# First, get dependencies normally to create a valid lock file
219+
:ok = Mix.Tasks.Deps.Get.run([])
220+
221+
# Read the lock file
222+
lock = Mix.Dep.Lock.read()
223+
{:hex, name, version, inner_checksum, managers, deps, repo, outer_checksum} = lock[:ex_doc]
224+
225+
assert_checksum_mismatch(%{
226+
ex_doc:
227+
{:hex, name, version, invalid_checksum(inner_checksum), managers, deps, repo,
228+
outer_checksum}
229+
})
230+
231+
assert_checksum_mismatch(%{
232+
ex_doc:
233+
{:hex, name, version, inner_checksum, managers, deps, repo,
234+
invalid_checksum(outer_checksum)}
235+
})
236+
end)
237+
end
238+
239+
defp assert_checksum_mismatch(lock) do
240+
File.write!("mix.lock", inspect(lock, limit: :infinity, pretty: true))
241+
Mix.Task.clear()
242+
243+
# The bug causes this to silently pass and rewrite the lock file with correct checksums
244+
assert_raise Mix.Error, ~r/Registry checksum mismatch against lock/, fn ->
245+
Mix.Tasks.Deps.Get.run([])
246+
end
247+
end
248+
249+
defp invalid_checksum("0" <> rest), do: "1" <> rest
250+
defp invalid_checksum(<<_::binary-size(1), rest::binary>>), do: "0" <> rest
201251
end

0 commit comments

Comments
 (0)