File 1387-erts-stdlib-Fix-ets-update_counter-4.patch of Package erlang

From 1e9ed964687e967b04d273ce7c7d6334e1eeab05 Mon Sep 17 00:00:00 2001
From: Jan Uhlig <juhlig@hnc-agency.org>
Date: Mon, 2 Feb 2026 13:30:17 +0100
Subject: [PATCH] erts,stdlib: Fix ets:update_counter/4

Reject a too small default tuple without key element
and thereby avoid dangerous out of bound key accesses.

For backward (bug)-compatibility reason we only badarg
if the key does not exist and bad default tuple is actually used.
---
 erts/emulator/beam/erl_db.c          |  3 ++-
 erts/emulator/beam/erl_db_hash.c     |  5 ++++-
 erts/emulator/beam/erl_db_tree.c     |  4 +++-
 lib/stdlib/src/erl_stdlib_errors.erl |  9 ++++++++-
 lib/stdlib/test/ets_SUITE.erl        | 23 ++++++++++++++++++++++-
 5 files changed, 39 insertions(+), 5 deletions(-)

diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c
index 8f5e1a9543..fae34e5274 100644
--- a/erts/emulator/beam/erl_db.c
+++ b/erts/emulator/beam/erl_db.c
@@ -58,6 +58,7 @@
 #define EXI_POSITION am_position /* The position is out of range. */
 #define EXI_OWNER    am_owner	 /* The receiving process is already the owner. */
 #define EXI_NOT_OWNER am_not_owner /* The current process is not the owner. */
+#define EXI_DEFAULT  am_default /* Invalid default tuple */
 
 erts_atomic_t erts_ets_misc_mem_size;
 
@@ -1335,7 +1336,7 @@ do_update_counter(Process *p, DbTable* tb,
     }
 
     if (!tb->common.meth->db_lookup_dbterm(p, tb, arg2, arg4, &handle)) {
-	p->fvalue = EXI_BAD_KEY;
+        p->fvalue = is_value(arg4) ? EXI_DEFAULT : EXI_BAD_KEY;
 	cret = DB_ERROR_BADPARAM;
 	goto bail_out; /* key not found */
     }
diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c
index fa2fa7e67e..1c52bd2282 100644
--- a/erts/emulator/beam/erl_db_hash.c
+++ b/erts/emulator/beam/erl_db_hash.c
@@ -3839,7 +3839,10 @@ db_lookup_dbterm_hash(Process *p, DbTable *tbl, Eterm key, Eterm obj,
         int arity = arityval(*objp);
         Eterm *htop, *hend;
 
-        ASSERT(arity >= tb->common.keypos);
+        if (arity < tb->common.keypos) {
+            WUNLOCK_HASH_LCK_CTR(lck_ctr);
+            return 0;
+        }
         htop = HAlloc(p, arity + 1);
         hend = htop + arity + 1;
         sys_memcpy(htop, objp, sizeof(Eterm) * (arity + 1));
diff --git a/erts/emulator/beam/erl_db_tree.c b/erts/emulator/beam/erl_db_tree.c
index c1ad86945f..f36b1c6c3c 100644
--- a/erts/emulator/beam/erl_db_tree.c
+++ b/erts/emulator/beam/erl_db_tree.c
@@ -3422,7 +3422,9 @@ int db_lookup_dbterm_tree_common(Process *p, DbTable *tbl, TreeDbTerm **root,
             int arity = arityval(*objp);
             Eterm *htop, *hend;
 
-            ASSERT(arity >= tbl->common.keypos);
+            if (arity < tbl->common.keypos) {
+                return 0;
+            }
             htop = HAlloc(p, arity + 1);
             hend = htop + arity + 1;
             sys_memcpy(htop, objp, sizeof(Eterm) * (arity + 1));
diff --git a/lib/stdlib/src/erl_stdlib_errors.erl b/lib/stdlib/src/erl_stdlib_errors.erl
index 45005047bc..1b4528989f 100644
--- a/lib/stdlib/src/erl_stdlib_errors.erl
+++ b/lib/stdlib/src/erl_stdlib_errors.erl
@@ -745,7 +745,7 @@ format_ets_error(update_counter, [_,_,UpdateOp,Default]=Args, Cause) ->
         "" ->
             %% The table is OK. The error is in one or more of the
             %% other arguments.
-            TupleCause = format_tuple(Default),
+            TupleCause = format_default_tuple(Default, Cause),
             case Cause of
                 badkey ->
                     ["", bad_key, format_update_op(UpdateOp) | TupleCause];
@@ -828,6 +828,11 @@ format_non_negative_integer(N) ->
 format_object([_,Object|_]=Args, Cause) ->
     [format_cause(Args, Cause) | format_tuple(Object)].
 
+format_default_tuple(Tuple, default) when is_tuple(Tuple) ->
+    [<<"default tuple too small">>];
+format_default_tuple(Term, _Cause) ->
+    format_tuple(Term).
+
 format_tuple(Term) ->
     if tuple_size(Term) > 0 -> [""];
        is_tuple(Term) -> [empty_tuple];
@@ -877,6 +882,8 @@ format_cause(Args, Cause) ->
         owner ->
             "";
         not_owner ->
+            "";
+        default ->
             ""
     end.
 
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index 142076bde0..ecf1b36e22 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -64,7 +64,8 @@
          fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1,
 	 update_element/1, update_counter/1, evil_update_counter/1, partly_bound/1, match_heavy/1]).
 -export([update_counter_with_default/1]).
--export([update_counter_with_default_bad_pos/1]).
+-export([update_counter_with_default_bad_pos/1,
+	 update_counter_with_default_bad_default/1]).
 -export([update_counter_table_growth/1]).
 -export([member/1]).
 -export([memory/1]).
@@ -152,6 +153,7 @@ all() ->
      update_counter, evil_update_counter,
      update_counter_with_default,
      update_counter_with_default_bad_pos,
+     update_counter_with_default_bad_default,
      partly_bound,
      update_counter_table_growth,
      match_heavy, {group, fold}, member, t_delete_object,
@@ -3033,6 +3035,25 @@ update_counter_with_default_bad_pos_do(Opts) ->
     0 = ets:info(T, size),
     ok.
 
+update_counter_with_default_bad_default(Config) when is_list(Config) ->
+    repeat_for_opts_all_set_table_types(fun update_counter_with_default_bad_default_do/1).
+
+update_counter_with_default_bad_default_do(Opts) ->
+    T = ets_new(a, [{keypos, 3} | Opts]),
+    0 = ets:info(T, size),
+    ok = try ets:update_counter(T, key, {2, 1}, {0, 0})
+	 catch
+	     error:badarg -> ok;
+	     Class:Reason -> {Class, Reason}
+	 end,
+    0 = ets:info(T, size),
+
+    %% Ignore bad default object if key exist (for backward bug-compat)
+    true = ets:insert(T, {0,0,key}),
+    1 = ets:update_counter(T, key, {2, 1}, {0, 0}),
+    1 = ets:info(T, size),
+    ok.
+
 update_counter_table_growth(_Config) ->
     repeat_for_opts(fun update_counter_table_growth_do/1).
 
-- 
2.51.0

openSUSE Build Service is sponsored by