From b20f19cf1d478d35d519657d6f9cc617fa90fe5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Tue, 23 Jun 2026 08:52:26 +0200 Subject: [PATCH 1/6] select_val (AArch64): Clarify that code generation is safe Refactor the code in `emit_i_jump_on_val()` for AArch64 to explicitly initialize the `fail` variable when `Fail` is not a label. The previous version of the code depended on the knowledge that `always_small(Src)` and `Fail.isNil()` could not be true at the same time. --- erts/emulator/beam/jit/arm/instr_select.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erts/emulator/beam/jit/arm/instr_select.cpp b/erts/emulator/beam/jit/arm/instr_select.cpp index 14e671753041..4fd0c6854722 100644 --- a/erts/emulator/beam/jit/arm/instr_select.cpp +++ b/erts/emulator/beam/jit/arm/instr_select.cpp @@ -456,6 +456,11 @@ void BeamModuleAssembler::emit_i_jump_on_val(const ArgSource &Src, ASSERT(Size.get() == args.size()); + if (Fail.isNil()) { + /* NIL means fallthrough to the next instruction. */ + fail = a.new_label(); + } + if (always_small(Src)) { comment("(skipped type test)"); } else { @@ -465,10 +470,6 @@ void BeamModuleAssembler::emit_i_jump_on_val(const ArgSource &Src, if (Fail.isLabel()) { a.b_ne(resolve_beam_label(Fail, disp1MB)); } else { - /* NIL means fallthrough to the next instruction. */ - ASSERT(Fail.isNil()); - - fail = a.new_label(); a.b_ne(fail); } } @@ -511,7 +512,7 @@ void BeamModuleAssembler::emit_i_jump_on_val(const ArgSource &Src, } } - if (Fail.getType() == ArgVal::Type::Immediate) { + if (Fail.isNil()) { a.bind(fail); } } From 38bdf604a680df03cee58bc34a4391bca17d364d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Tue, 23 Jun 2026 09:46:06 +0200 Subject: [PATCH 2/6] AArch64: Correct emit_is_lt() Given the following code: -module(bug). -export([foo/2]). foo(A, B) when is_integer(A), A < 10, is_integer(B), 0 < B, B < 100 -> bar(A, B). bar(A, B) when A < B -> ok. calling `bug:foo/2` with a large negative bignum as the first argument would fail: 1> bug:foo(-1 bsl 64, 5). ** exception error: no function clause matching bug:bar(-18446744073709551616,5) --- erts/emulator/beam/jit/arm/instr_common.cpp | 2 +- erts/emulator/test/op_SUITE.erl | 175 ++++++++++++++++++-- 2 files changed, 165 insertions(+), 12 deletions(-) diff --git a/erts/emulator/beam/jit/arm/instr_common.cpp b/erts/emulator/beam/jit/arm/instr_common.cpp index 6b0d157b6b70..40e0641acaa6 100644 --- a/erts/emulator/beam/jit/arm/instr_common.cpp +++ b/erts/emulator/beam/jit/arm/instr_common.cpp @@ -2204,7 +2204,7 @@ void BeamModuleAssembler::emit_is_lt(const ArgLabel &Fail, Label next = a.new_label(); comment("simplified test because it always succeeds when LHS is a " "bignum"); - emit_is_not_boxed(next, rhs.reg); + emit_is_not_boxed(next, lhs.reg); a.cmp(lhs.reg, rhs.reg); a.b_ge(resolve_beam_label(Fail, disp1MB)); a.bind(next); diff --git a/erts/emulator/test/op_SUITE.erl b/erts/emulator/test/op_SUITE.erl index a6aac9eb8b4a..6a8da54b823e 100644 --- a/erts/emulator/test/op_SUITE.erl +++ b/erts/emulator/test/op_SUITE.erl @@ -141,7 +141,7 @@ t_not(Config) when is_list(Config) -> run_test_module(Cases, false), {comment,integer_to_list(length(Cases)) ++ " cases"}. -%% Test that simlpe relations between relation operators hold. +%% Test that simple relations between relation operators hold. relop_simple(Config) when is_list(Config) -> Big1 = 19738924729729787487784874, Big2 = 38374938373887374983978484, @@ -170,19 +170,33 @@ relop_simple_do(V1,V2) -> %%io:format("compare ~p\n and ~p\n",[V1,V2]), L = V1 < V2, - L = not (V1 >= V2), + L = not id(V1 >= V2), L = V2 > V1, - L = not (V2 =< V1), - - G = V1 > V2, - G = not (V1 =< V2), - G = V2 < V1, - G = not (V2 >= V1), + L = not id(V2 =< V1), + + LE = V1 =< V2, + LE = not id(V1 > V2), + LE = V2 >= V1, + LE = not id(V2 < V1), + + G = id(if + V1 > V2 -> id(true); + true -> false + end), + G = id(V1 > V2), + G = not id(V1 =< V2), + G = id(V2 < V1), + G = not id(V2 >= V1), + + GE = id(V1 >= V2), + GE = not id(V1 < V2), + GE = id(V2 =< V1), + GE = not id(V2 > V1), ID = V1 =:= V2, ID = V2 =:= V1, - ID = not (V1 =/= V2), - ID = not (V2 =/= V1), + ID = not id(V1 =/= V2), + ID = not id(V2 =/= V1), implies(ID, V1 == V2), @@ -196,7 +210,146 @@ relop_simple_do(V1,V2) -> {false, true, false, false, 0} -> ok; {false, true, true, false, 0} -> ok; {false, false, false, true, +1} -> ok - end. + end, + + LE = L orelse EQ, + GE = G orelse EQ, + + relop_simple_do1(id(V1), id(V2)). + +relop_simple_do1(V1, V2) -> + if + is_integer(V1), -1 bsl 56 =< V1, V1 =< 1 bsl 56, + is_integer(V2), V2 < 1 bsl 56 -> + L = id(if + V1 < V2 -> id(true); + true -> false + end), + L = id(V1 < V2), + L = not id(V1 >= V2), + L = id(V2 > V1), + L = not id(V2 =< V1), + + LE = id(if + V1 =< V2 -> id(true); + true -> false + end), + LE = id(V1 =< V2), + LE = not id(V1 > V2), + LE = id(V2 >= V1), + LE = not id(V2 < V1), + + G = id(if + V1 > V2 -> id(true); + true -> false + end), + G = id(V1 > V2), + G = not id(V1 =< V2), + G = id(V2 < V1), + G = not id(V2 >= V1), + + GE = id(if + V1 >= V2 -> id(true); + true -> false + end), + GE = id(V1 >= V2), + GE = not id(V1 < V2), + GE = id(V2 =< V1), + GE = not id(V2 > V1), + + ok; + true -> + ok + end, + relop_simple_do2(id(V1), id(V2)). + +relop_simple_do2(V1, V2) -> + if + is_integer(V1), -1 bsl 56 =< V1, V1 =< 1 bsl 56, + is_integer(V2), V2 > 1 bsl 50 -> + L = id(if + V1 < V2 -> id(true); + true -> false + end), + L = id(V1 < V2), + L = not id(V1 >= V2), + L = id(V2 > V1), + L = not id(V2 =< V1), + + LE = id(if + V1 =< V2 -> id(true); + true -> false + end), + LE = id(V1 =< V2), + LE = not id(V1 > V2), + LE = id(V2 >= V1), + LE = not id(V2 < V1), + + G = id(V1 > V2), + G = not id(V1 =< V2), + G = id(V2 < V1), + G = not id(V2 >= V1), + + GE = id(if + V1 >= V2 -> id(true); + true -> false + end), + GE = id(V1 >= V2), + GE = not id(V1 < V2), + GE = id(V2 =< V1), + GE = not id(V2 > V1), + + ok; + true -> + ok + end, + relop_simple_do3(id(V1), id(V2)). + +relop_simple_do3(V1, V2) -> + if + is_integer(V1), + is_integer(V2), -1 bsl 56 =< V2, V2 =< 1 bsl 56 -> + L = id(if + V1 < V2 -> id(true); + true -> false + end), + L = id(V1 < V2), + L = not id(V1 >= V2), + L = id(V2 > V1), + L = not id(V2 =< V1), + + LE = id(if + V1 =< V2 -> id(true); + true -> false + end), + LE = id(V1 =< V2), + LE = not id(V1 > V2), + LE = id(V2 >= V1), + LE = not id(V2 < V1), + + G = id(if + V1 > V2 -> id(true); + true -> false + end), + G = V1 > V2, + G = not id(V1 =< V2), + G = V2 < V1, + G = not id(V2 >= V1), + + GE = id(if + V1 >= V2 -> id(true); + true -> false + end), + GE = id(V1 >= V2), + GE = not id(V1 < V2), + GE = id(V2 =< V1), + GE = not id(V2 > V1), + + ok; + true -> + ok + end, + ok. implies(false, _) -> ok; implies(true, true) -> ok. From a7d789c1396cefff7b157ad2210ea6a7ec027b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Wed, 24 Jun 2026 06:30:27 +0200 Subject: [PATCH 3/6] Fix buffer size in call to `erts_snprintf()` --- erts/emulator/beam/jit/beam_jit_common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erts/emulator/beam/jit/beam_jit_common.cpp b/erts/emulator/beam/jit/beam_jit_common.cpp index ab559910d1bd..cfa232c5f759 100644 --- a/erts/emulator/beam/jit/beam_jit_common.cpp +++ b/erts/emulator/beam/jit/beam_jit_common.cpp @@ -404,7 +404,7 @@ void *BeamModuleAssembler::register_metadata(const BeamCodeHeader *header) { } n = erts_snprintf(name_buffer, - 1024, + sizeof(name_buffer), "%T:%T/%d", ci->mfa.module, ci->mfa.function, From e7912cfd8435c0202ad0aa9e023087e395dd3c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Wed, 24 Jun 2026 11:44:40 +0200 Subject: [PATCH 4/6] Verify the range of labels in the lambda chunk This is an early rejection of damaged BEAM files, not a mitigation of a vulnerability, because anyone that can control the contents of BEAM files could do much worse harm. --- erts/emulator/beam/beam_file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erts/emulator/beam/beam_file.c b/erts/emulator/beam/beam_file.c index 8a43070f7496..20eae7d76420 100644 --- a/erts/emulator/beam/beam_file.c +++ b/erts/emulator/beam/beam_file.c @@ -394,8 +394,11 @@ static int parse_lambda_chunk(BeamFile *beam, IFF_Chunk *chunk) { BeamFile_AtomTable *atoms; BeamReader reader; Sint32 count; + Uint label_count; int i; + label_count = beam->code.label_count; + lambdas = &beam->lambdas; ASSERT(lambdas->entries == NULL); @@ -421,7 +424,7 @@ static int parse_lambda_chunk(BeamFile *beam, IFF_Chunk *chunk) { LoadAssert(beamreader_read_i32(&reader, &old_uniq)); LoadAssert(atom_index >= 0 && atom_index < atoms->count); - LoadAssert(label >= 0); + LoadAssert(label > 0 && label < label_count); lambdas->entries[i].function = atoms->entries[atom_index]; lambdas->entries[i].num_free = num_free; From 5cebc72339ca274591abbfb5af43c88c6179959d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 26 Jun 2026 12:13:41 +0200 Subject: [PATCH 5/6] AArch64: Correct put_list_deallocate When there were at least 32 stack slots, the code generated for `put_list_deallocate` would use a post-increment offset that was out of range. --- erts/emulator/beam/jit/arm/instr_common.cpp | 6 +++-- erts/emulator/test/beam_SUITE.erl | 30 +++++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/erts/emulator/beam/jit/arm/instr_common.cpp b/erts/emulator/beam/jit/arm/instr_common.cpp index 40e0641acaa6..a6d113e11745 100644 --- a/erts/emulator/beam/jit/arm/instr_common.cpp +++ b/erts/emulator/beam/jit/arm/instr_common.cpp @@ -730,7 +730,8 @@ void BeamModuleAssembler::emit_put_list_deallocate(const ArgSource &Hd, ASSERT(dealloc < MAX_REG * sizeof(Eterm)); - if (Hd.isYRegister() && !Tl.isYRegister() && dealloc > 0) { + if (Hd.isYRegister() && !Tl.isYRegister() && dealloc > 0 && + Support::is_int_n<9>(dealloc)) { auto hd_index = Hd.as().get(); if (hd_index == 0) { @@ -741,7 +742,8 @@ void BeamModuleAssembler::emit_put_list_deallocate(const ArgSource &Hd, tl_reg = load_source(Tl, TMP2).reg; dealloc = 0; } - } else if (!Hd.isYRegister() && Tl.isYRegister() && dealloc > 0) { + } else if (!Hd.isYRegister() && Tl.isYRegister() && dealloc > 0 && + Support::is_int_n<9>(dealloc)) { auto tl_index = Tl.as().get(); if (tl_index == 0) { diff --git a/erts/emulator/test/beam_SUITE.erl b/erts/emulator/test/beam_SUITE.erl index 64cb701241c4..6f4b5c940fe4 100644 --- a/erts/emulator/test/beam_SUITE.erl +++ b/erts/emulator/test/beam_SUITE.erl @@ -28,7 +28,7 @@ heap_sizes/1, big_lists/1, fconv/1, select_val/1, select_tuple_arity/1, swap_temp_apply/1, beam_init_yregs/1, - beam_register_cache/1]). + beam_register_cache/1, put_list_dealloc/1]). -export([applied/2,swap_temp_applied/1]). @@ -42,7 +42,7 @@ all() -> heap_sizes, big_lists, fconv, select_val, select_tuple_arity, swap_temp_apply, beam_init_yregs, - beam_register_cache]. + beam_register_cache, put_list_dealloc]. groups() -> []. @@ -529,6 +529,32 @@ beam_register_cache(Config) -> ok. +put_list_dealloc(_Config) -> + Seq = lists:seq(1, 32), + RevSeq = lists:reverse(Seq), + RevSeq = do_put_list_dealloc(head, Seq), + + HdRevSeq = hd(RevSeq), + TlRevSeq = tl(RevSeq), + [TlRevSeq|HdRevSeq] = do_put_list_dealloc(tail, Seq), + + ok. + +do_put_list_dealloc(Where, List0) -> + [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z, + Å,Ä,Ö,AA,AE,OE] = List0, + put(force_vars_to_stack, true), + List = [AE,AA,Ö,Ä,Å,Z,Y,X,W,V,U,T,S,R,Q,P,O,N,M,L,K,J, + I,H,G,F,E,D,C,B,A], + %% put_list and dealloc would be combined into put_list_dealloc + %% with deallocation being done as post-increment in the ldr + %% instruction loading y0. With 32 or more Y registers, the + %% post-increment offset would exceed its upper limit. + case Where of + head -> [OE|List]; + tail -> [List|OE] + end. + %%% Common utilities. spawn_exec(F) -> {Pid,Ref} = spawn_monitor(fun() -> From 90d4d1a2049dfc72a131f81af0fd1b6d83f6259a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 26 Jun 2026 13:01:16 +0200 Subject: [PATCH 6/6] AArc64: Fix stacktrace in exception from `and` The stacktrace would point out `or/2`. --- erts/emulator/beam/jit/arm/instr_guard_bifs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp b/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp index a1c5dd51fbf1..9599d4d2034f 100644 --- a/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp +++ b/erts/emulator/beam/jit/arm/instr_guard_bifs.cpp @@ -322,7 +322,7 @@ void BeamModuleAssembler::emit_bif_and(const ArgLabel &Fail, a.b_eq(next); mov_var(XREG0, src1); mov_var(XREG1, src2); - fragment_call(ga->get_handle_or_error()); + fragment_call(ga->get_handle_and_error()); } a.bind(next);