From e6bd2905d73303477ba3ee1bbb62529dba9f29d6 Mon Sep 17 00:00:00 2001 From: Matthew Newville Date: Tue, 14 Jan 2025 10:33:36 -0600 Subject: [PATCH 1/4] remove linalg, as documented --- asteval/astutils.py | 92 ++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/asteval/astutils.py b/asteval/astutils.py index 4dab4fc..2052683 100644 --- a/asteval/astutils.py +++ b/asteval/astutils.py @@ -122,7 +122,7 @@ 'insert', 'int32', 'integer', 'interp', 'intersect1d', 'invert', 'iscomplex', 'iscomplexobj', 'isfinite', 'isinf', 'isnan', 'isneginf', 'isposinf', 'isreal', 'isrealobj', 'isscalar', 'iterable', 'kaiser', - 'kron', 'ldexp', 'left_shift', 'less', 'less_equal', 'linalg', 'linspace', + 'kron', 'ldexp', 'left_shift', 'less', 'less_equal', 'linspace', 'little_endian', 'loadtxt', 'log', 'log10', 'log1p', 'log2', 'logaddexp', 'logaddexp2', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'logspace', 'longdouble', 'longlong', 'mask_indices', 'matrix', 'maximum', @@ -506,59 +506,59 @@ def __init__(self, name, interp, doc=None, lineno=0, self.name = name self.__name__ = self.name self.__asteval__ = interp - self.__raise_exc__ = self.__asteval__.raise_exception + self.raise_exc = self.__asteval__.raise_exception self.__doc__ = doc - self.__body__ = body - self.__argnames__ = args - self.__kwargs__ = kwargs - self.__vararg__ = vararg - self.__varkws__ = varkws + self.body = body + self.argnames = args + self.kwargs = kwargs + self.vararg = vararg + self.varkws = varkws self.lineno = lineno self.__ininit__ = False def __setattr__(self, attr, val): if not getattr(self, '__ininit__', True): - self.__raise_exc__(None, exc=TypeError, + self.raise_exc(None, exc=TypeError, msg="procedure is read-only") self.__dict__[attr] = val def __dir__(self): - return ['__getdoc__', '__argnames__', 'kwargs', 'name', 'vararg', 'varkws'] + return ['_getdoc', 'argnames', 'kwargs', 'name', 'vararg', 'varkws'] - def __getdoc__(self): + def _getdoc(self): doc = self.__doc__ if isinstance(doc, ast.Constant): doc = doc.value return doc def __repr__(self): - """Procedure repr""" - sig = self.__signature__() + """TODO: docstring in magic method.""" + sig = self._signature() rep = f"" - doc = self.__getdoc__() + doc = self._getdoc() if doc is not None: rep = f"{rep}\n {doc}" return rep - def __signature__(self): - "return the procedure's call signature" + def _signature(self): + "call signature" sig = "" - if len(self.__argnames__) > 0: - sig = sig + ', '.join(self.__argnames__) - if self.__vararg__ is not None: - sig = sig + f"*{self.__vararg__}" - if len(self.__kwargs__) > 0: + if len(self.argnames) > 0: + sig = sig + ', '.join(self.argnames) + if self.vararg is not None: + sig = sig + f"*{self.vararg}" + if len(self.kwargs) > 0: if len(sig) > 0: sig = f"{sig}, " - _kw = [f"{k}={v}" for k, v in self.__kwargs__] + _kw = [f"{k}={v}" for k, v in self.kwargs] sig = f"{sig}{', '.join(_kw)}" - if self.__varkws__ is not None: - sig = f"{sig}, **{self.__varkws__}" + if self.varkws is not None: + sig = f"{sig}, **{self.varkws}" return f"{self.name}({sig})" def __call__(self, *args, **kwargs): - """call the Procedure""" + """TODO: docstring in public method.""" topsym = self.__asteval__.symtable if self.__asteval__.config.get('nested_symtable', False): sargs = {'_main': topsym} @@ -576,27 +576,27 @@ def __call__(self, *args, **kwargs): args = list(args) nargs = len(args) nkws = len(kwargs) - nargs_expected = len(self.__argnames__) + nargs_expected = len(self.argnames) # check for too few arguments, but the correct keyword given if (nargs < nargs_expected) and nkws > 0: - for name in self.__argnames__[nargs:]: + for name in self.argnames[nargs:]: if name in kwargs: args.append(kwargs.pop(name)) nargs = len(args) - nargs_expected = len(self.__argnames__) + nargs_expected = len(self.argnames) nkws = len(kwargs) if nargs < nargs_expected: msg = f"{self.name}() takes at least" msg = f"{msg} {nargs_expected} arguments, got {nargs}" - self.__raise_exc__(None, exc=TypeError, msg=msg) + self.raise_exc(None, exc=TypeError, msg=msg) # check for multiple values for named argument - if len(self.__argnames__) > 0 and kwargs is not None: + if len(self.argnames) > 0 and kwargs is not None: msg = "multiple values for keyword argument" - for targ in self.__argnames__: + for targ in self.argnames: if targ in kwargs: msg = f"{msg} '{targ}' in Procedure {self.name}" - self.__raise_exc__(None, exc=TypeError, msg=msg, lineno=self.lineno) + self.raise_exc(None, exc=TypeError, msg=msg, lineno=self.lineno) # check more args given than expected, varargs not given if nargs != nargs_expected: @@ -604,44 +604,44 @@ def __call__(self, *args, **kwargs): if nargs < nargs_expected: msg = f"not enough arguments for Procedure {self.name}()" msg = f"{msg} (expected {nargs_expected}, got {nargs}" - self.__raise_exc__(None, exc=TypeError, msg=msg) + self.raise_exc(None, exc=TypeError, msg=msg) - if nargs > nargs_expected and self.__vararg__ is None: - if nargs - nargs_expected > len(self.__kwargs__): + if nargs > nargs_expected and self.vararg is None: + if nargs - nargs_expected > len(self.kwargs): msg = f"too many arguments for {self.name}() expected at most" - msg = f"{msg} {len(self.__kwargs__)+nargs_expected}, got {nargs}" - self.__raise_exc__(None, exc=TypeError, msg=msg) + msg = f"{msg} {len(self.kwargs)+nargs_expected}, got {nargs}" + self.raise_exc(None, exc=TypeError, msg=msg) for i, xarg in enumerate(args[nargs_expected:]): - kw_name = self.__kwargs__[i][0] + kw_name = self.kwargs[i][0] if kw_name not in kwargs: kwargs[kw_name] = xarg - for argname in self.__argnames__: + for argname in self.argnames: symlocals[argname] = args.pop(0) try: - if self.__vararg__ is not None: - symlocals[self.__vararg__] = tuple(args) + if self.vararg is not None: + symlocals[self.vararg] = tuple(args) - for key, val in self.__kwargs__: + for key, val in self.kwargs: if key in kwargs: val = kwargs.pop(key) symlocals[key] = val - if self.__varkws__ is not None: - symlocals[self.__varkws__] = kwargs + if self.varkws is not None: + symlocals[self.varkws] = kwargs elif len(kwargs) > 0: msg = f"extra keyword arguments for Procedure {self.name}: " msg = msg + ','.join(list(kwargs.keys())) - self.__raise_exc__(None, msg=msg, exc=TypeError, + self.raise_exc(None, msg=msg, exc=TypeError, lineno=self.lineno) except (ValueError, LookupError, TypeError, NameError, AttributeError): msg = f"incorrect arguments for Procedure {self.name}" - self.__raise_exc__(None, msg=msg, lineno=self.lineno) + self.raise_exc(None, msg=msg, lineno=self.lineno) if self.__asteval__.config.get('nested_symtable', False): save_symtable = self.__asteval__.symtable @@ -655,7 +655,7 @@ def __call__(self, *args, **kwargs): retval = None # evaluate script of function - for node in self.__body__: + for node in self.body: self.__asteval__.run(node, expr='<>', lineno=self.lineno) if len(self.__asteval__.error) > 0: break From e889a135cccf845ea5a822a5604ba46cf240e885 Mon Sep 17 00:00:00 2001 From: Matthew Newville Date: Tue, 14 Jan 2025 10:34:18 -0600 Subject: [PATCH 2/4] uddate doc description for security advisory --- doc/motivation.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/doc/motivation.rst b/doc/motivation.rst index df4c051..c8160d6 100644 --- a/doc/motivation.rst +++ b/doc/motivation.rst @@ -108,10 +108,16 @@ needed, these modules can be added to any Interpreter either using the ``user_symbols`` argument when creating it, or adding the needed symbols to the symbol table after the Interpreter is created. -There are important categories of safety that asteval may attempt to address, -but cannot guarantee success. The most important of these is resource hogging, -which might be used for a denial-of-service attack. There is no guaranteed -timeout on any calculation, and so a reasonable looking calculation such as:: +In 2025, a security audit by William Khem Marquez showed a +vulnerability from leaving some AST objects exposed within the +interpreter for user-defined functions ("Procedures"), and this was +fixed for version 1.0.6. + +There are other categories of safety that asteval may attempt to +address, but cannot guarantee success. The most important of these is +resource hogging, which might be used for a denial-of-service attack. +There is no guaranteed timeout on any calculation, and so a reasonable +looking calculation such as:: from asteval import Interpreter aeval = Interpreter() From 10c8fbf308e4ddb659a84c12e8a3a4e85c9f47ea Mon Sep 17 00:00:00 2001 From: Matthew Newville Date: Tue, 14 Jan 2025 10:34:32 -0600 Subject: [PATCH 3/4] doc config --- doc/Makefile | 6 +++++- doc/conf.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index 5a777c0..02d908f 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -4,7 +4,7 @@ # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build -PAPER = +PAPER = letter BUILDDIR = _build INSTALLDIR = /home/newville/public_html/asteval/ @@ -31,6 +31,10 @@ html: @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." +tarball: all + cd $(BUILDDIR)/html && tar cvzf ../../../_doc.tgz * & cd ../../ + + pdf: latex cd $(BUILDDIR)/latex && make all-pdf cp -pr $(BUILDDIR)/latex/asteval.pdf ./asteval.pdf diff --git a/doc/conf.py b/doc/conf.py index b611cdd..ad1de14 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -172,7 +172,7 @@ # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' From 0508431f6e9f5ba46b6f097487e336ae614f4e37 Mon Sep 17 00:00:00 2001 From: Matthew Newville Date: Tue, 14 Jan 2025 10:39:47 -0600 Subject: [PATCH 4/4] more doc config --- doc/Makefile | 11 ++++------- doc/conf.py | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index 02d908f..695e2af 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -4,16 +4,11 @@ # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build -PAPER = letter BUILDDIR = _build - INSTALLDIR = /home/newville/public_html/asteval/ - # Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) . .PHONY: html pdf all help clean dirhtml latex @@ -32,7 +27,9 @@ html: @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." tarball: all - cd $(BUILDDIR)/html && tar cvzf ../../../_doc.tgz * & cd ../../ + cd $(BUILDDIR)/html && tar cvzf ../../../_doc.tgz * + cd ../../ + @echo pdf: latex diff --git a/doc/conf.py b/doc/conf.py index ad1de14..b80dd0b 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -172,7 +172,7 @@ # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -latex_paper_size = 'letter' +latex_papersize = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt'