From 42483617f1fd47c81bbf66c71c2ce5cbe2d3df29 Mon Sep 17 00:00:00 2001 From: Daniel Fairhead Date: Fri, 13 Mar 2026 13:27:47 +0000 Subject: [PATCH 4/4] Fix unhashable items inside tuples bug. CVE: CVE-2026-32640 Upstream-Status: Backport [https://github.com/danthedeckie/simpleeval/commit/d1e4569db678a3cb42b779f404aa203665d52ab0] Backport Changes: - Omit the upstream 1.0.6 version metadata change and retain the 0.9.13 setuptools build configuration. - Remove the standalone Hashable import added by the preceding 0.9.13 backport while retaining the target Python compatibility flags. (cherry picked from commit d1e4569db678a3cb42b779f404aa203665d52ab0) Signed-off-by: Hetvi Thakar --- simpleeval.py | 16 +++++++++++++--- test_simpleeval.py | 9 +++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/simpleeval.py b/simpleeval.py index 9f3003e..a6745db 100644 --- a/simpleeval.py +++ b/simpleeval.py @@ -103,7 +103,6 @@ import sys import types import warnings from random import random -from typing import Hashable PYTHON3 = sys.version_info[0] == 3 PYTHON35 = PYTHON3 and sys.version_info > (3, 5) @@ -119,6 +118,17 @@ MAX_SHIFT_BASE = int(sys.float_info.max) # highest on left side of << or >> DISALLOW_PREFIXES = ["_", "func_"] DISALLOW_METHODS = ["format", "format_map", "mro"] +######################################## +# Tiny helpers: + + +def is_hashable(value): + try: + return hash(value) + except TypeError: + return False + + # Disallow functions: # This, strictly speaking, is not necessary. These /should/ never be accessable anyway, # if DISALLOW_PREFIXES and DISALLOW_METHODS are all right. This is here to try and help @@ -431,7 +441,7 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods """ if isinstance(item, types.ModuleType): raise FeatureNotAvailable("Sorry, modules are not allowed") - if isinstance(item, Hashable) and item in DISALLOW_FUNCTIONS: + if is_hashable(item) and item in DISALLOW_FUNCTIONS: raise FeatureNotAvailable("This function is forbidden") if not isinstance(item, (dict, list, tuple, set, frozenset)): @@ -657,7 +667,7 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods if item is not _ATTR_NOT_FOUND: if isinstance(item, types.ModuleType): raise FeatureNotAvailable("Sorry, modules are not allowed in attribute access") - if isinstance(item, Hashable) and item in DISALLOW_FUNCTIONS: + if is_hashable(item) and item in DISALLOW_FUNCTIONS: raise FeatureNotAvailable("This function is forbidden") return item diff --git a/test_simpleeval.py b/test_simpleeval.py index f3c9a60..a654f6d 100644 --- a/test_simpleeval.py +++ b/test_simpleeval.py @@ -347,6 +347,15 @@ class TestFunctions(DRYTest): self.t("foo(mult=2, to_return=4)", 8) self.t("foo(2, 10)", 20) + def test_function_with_list_args(self): + # Regression test, makes sure we can pass lists (non-hashable) items as + # kwargs to functions. + + def func(*args, **kwargs): + return 42 + + simple_eval("test(boo=x)", functions={"test": func}, names={"x": [1, 2]}) + class TestOperators(DRYTest): """Test adding in new operators, removing them, make sure it works.""" -- 2.35.6