diff --git a/CHANGES b/CHANGES
index 19f7d7f..f1bc247 100644
--- a/CHANGES
+++ b/CHANGES
@@ -4,6 +4,8 @@ Pint Changelog
 0.10 (unreleased)
 -----------------
 
+- Removed eval usage when creating UnitDefinition and PrefixDefinition from string.
+  (Issue #942)
 - Added `fmt_locale` argument to registry.
   (Issue #904)
 - Better error message when Babel is not installed.
diff --git a/pint/definitions.py b/pint/definitions.py
index 4188124..c0481df 100644
--- a/pint/definitions.py
+++ b/pint/definitions.py
@@ -13,6 +13,20 @@ from .errors import DefinitionSyntaxError
 from .util import ParserHelper, UnitsContainer, _is_dim
 
 
+class _NotNumeric(Exception):
+    def __init__(self, value):
+        self.value = value
+
+
+def numeric_parse(s):
+    ph = ParserHelper.from_string(s)
+
+    if len(ph):
+        raise _NotNumeric(s)
+
+    return ph.scale
+
+
 class Definition:
     """Base class for definitions.
 
@@ -94,7 +108,13 @@ class PrefixDefinition(Definition):
 
     def __init__(self, name, symbol, aliases, converter):
         if isinstance(converter, str):
-            converter = ScaleConverter(eval(converter))
+            try:
+                converter = ScaleConverter(numeric_parse(converter))
+            except _NotNumeric as ex:
+                raise ValueError(
+                    f"Prefix definition ('{name}') must contain only numbers, not {ex.value}"
+                )
+
         aliases = tuple(alias.strip("-") for alias in aliases)
         if symbol:
             symbol = symbol.strip("-")
@@ -114,10 +134,19 @@ class UnitDefinition(Definition):
         if isinstance(converter, str):
             if ";" in converter:
                 [converter, modifiers] = converter.split(";", 2)
-                modifiers = dict(
-                    (key.strip(), eval(value))
-                    for key, value in (part.split(":") for part in modifiers.split(";"))
-                )
+
+                try:
+                    modifiers = dict(
+                        (key.strip(), numeric_parse(value))
+                        for key, value in (
+                            part.split(":") for part in modifiers.split(";")
+                        )
+                    )
+                except _NotNumeric as ex:
+                    raise ValueError(
+                        f"Unit definition ('{name}') must contain only numbers in modifier, not {ex.value}"
+                    )
+
             else:
                 modifiers = {}
 

[SECURITY] Arbitrary code execution from definitions
# Critical security vulnerability

A user who is allowed to upload a definitions file can run arbitrary code as the user running pint, thanks to two calls to eval() in definitions.py,

here
https://github.com/hgrecco/pint/blob/23c44ee335797c051feea0103e28c6a7221b3b75/pint/definitions.py#L90-L96
and here
https://github.com/hgrecco/pint/blob/23c44ee335797c051feea0103e28c6a7221b3b75/pint/definitions.py#L103-L119

diff --git a/pint/testsuite/test_definitions.py b/pint/testsuite/test_definitions.py
index e80622f..a5e0086 100644
--- a/pint/testsuite/test_definitions.py
+++ b/pint/testsuite/test_definitions.py
@@ -19,6 +19,9 @@ class TestDefinition(BaseTestCase):
             Definition.from_string("[x] = [time] * meter")
 
     def test_prefix_definition(self):
+
+        self.assertRaises(ValueError, Definition.from_string, "m- = 1e-3 k")
+
         for definition in ("m- = 1e-3", "m- = 10**-3", "m- = 0.001"):
             x = Definition.from_string(definition)
             self.assertIsInstance(x, PrefixDefinition)
@@ -85,6 +88,12 @@ class TestDefinition(BaseTestCase):
         self.assertEqual(x.converter.scale, 6.28)
         self.assertEqual(x.reference, UnitsContainer(radian=1))
 
+        self.assertRaises(
+            ValueError,
+            Definition.from_string,
+            "degF = 9 / 5 * kelvin; offset: 255.372222 bla",
+        )
+
     def test_dimension_definition(self):
         x = DimensionDefinition("[time]", "", (), converter="")
         self.assertTrue(x.is_base)

pip install -e .[test]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-mpl
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi @ file:///croot/certifi_1671487769961/work/certifi
coverage==7.2.7
cycler==0.11.0
exceptiongroup==1.2.2
fonttools==4.38.0
importlib-metadata==6.7.0
importlib-resources==5.12.0
iniconfig==2.0.0
Jinja2==3.1.6
kiwisolver==1.4.5
MarkupSafe==2.1.5
matplotlib==3.5.3
numpy==1.21.6
packaging==24.0
Pillow==9.5.0
-e git+https://github.com/hgrecco/pint.git@d662fd2580acfbdb3cbae6c57969c2b67408ce96#egg=Pint
pluggy==1.2.0
pyparsing==3.1.4
pytest==7.4.4
pytest-cov==4.1.0
pytest-mpl==0.17.0
python-dateutil==2.9.0.post0
six==1.17.0
tomli==2.0.1
typing_extensions==4.7.1
zipp==3.15.0

name: pint
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.2.7
      - cycler==0.11.0
      - exceptiongroup==1.2.2
      - fonttools==4.38.0
      - importlib-metadata==6.7.0
      - importlib-resources==5.12.0
      - iniconfig==2.0.0
      - jinja2==3.1.6
      - kiwisolver==1.4.5
      - markupsafe==2.1.5
      - matplotlib==3.5.3
      - numpy==1.21.6
      - packaging==24.0
      - pillow==9.5.0
      - pluggy==1.2.0
      - pyparsing==3.1.4
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytest-mpl==0.17.0
      - python-dateutil==2.9.0.post0
      - six==1.17.0
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - zipp==3.15.0
prefix: /opt/conda/envs/pint

pint/testsuite/test_definitions.py::TestDefinition::test_prefix_definition pint/testsuite/test_definitions.py::TestDefinition::test_unit_definition
pint/testsuite/test_definitions.py::TestDefinition::test_alias_definition pint/testsuite/test_definitions.py::TestDefinition::test_baseunit_definition pint/testsuite/test_definitions.py::TestDefinition::test_dimension_definition pint/testsuite/test_definitions.py::TestDefinition::test_invalid
pint/definitions.py CHANGES
pint/definitions.py CHANGES