summaryrefslogtreecommitdiff
path: root/searx/plugins/calculator.py
diff options
context:
space:
mode:
authorMarkus Heiser <markus.heiser@darmarit.de>2024-08-21 17:50:44 +0200
committerMarkus Heiser <markus.heiser@darmarIT.de>2024-09-03 18:36:28 +0200
commit3a3ff8f02092491c159a76e76dd50f1b6fcadc70 (patch)
treefe521f0ef932dda80db0a955ef47a5fff5406a3a /searx/plugins/calculator.py
parent7d9d5186a04cddbdc6415bcd98d19fff12a2e7ba (diff)
downloadsearxng-3a3ff8f02092491c159a76e76dd50f1b6fcadc70.tar.gz
searxng-3a3ff8f02092491c159a76e76dd50f1b6fcadc70.zip
[mod] hardening "calculator plugin" / limit execution time to 50 ms
The execution of the function for the calculation is outsourced to a process whose runtime is limited to 50 milliseconds. Related: - [1] https://github.com/searxng/searxng/pull/3377#issuecomment-2067977375 Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
Diffstat (limited to 'searx/plugins/calculator.py')
-rw-r--r--searx/plugins/calculator.py43
1 files changed, 37 insertions, 6 deletions
diff --git a/searx/plugins/calculator.py b/searx/plugins/calculator.py
index cb5425e90..aef10c559 100644
--- a/searx/plugins/calculator.py
+++ b/searx/plugins/calculator.py
@@ -4,10 +4,13 @@
import ast
import operator
+from multiprocessing import Process, Queue
from flask_babel import gettext
from searx import settings
+from searx.plugins import logger
+
name = "Basic Calculator"
description = gettext("Calculate mathematical expressions via the search bar")
default_on = False
@@ -15,6 +18,8 @@ default_on = False
preference_section = 'general'
plugin_id = 'calculator'
+logger = logger.getChild(plugin_id)
+
operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
@@ -51,6 +56,30 @@ def _eval(node):
raise TypeError(node)
+def timeout_func(timeout, func, *args, **kwargs):
+
+ def handler(q: Queue, func, args, **kwargs): # pylint:disable=invalid-name
+ try:
+ q.put(func(*args, **kwargs))
+ except:
+ q.put(None)
+ raise
+
+ que = Queue()
+ p = Process(target=handler, args=(que, func, args), kwargs=kwargs)
+ p.start()
+ p.join(timeout=timeout)
+ ret_val = None
+ if not p.is_alive():
+ ret_val = que.get()
+ else:
+ logger.debug("terminate function after timeout is exceeded")
+ p.terminate()
+ p.join()
+ p.close()
+ return ret_val
+
+
def post_search(_request, search):
# don't run on public instances due to possible attack surfaces
if settings['server']['public_instance']:
@@ -74,13 +103,15 @@ def post_search(_request, search):
# in python, powers are calculated via **
query_py_formatted = query.replace("^", "**")
- try:
- result = str(_eval_expr(query_py_formatted))
- if result != query:
- search.result_container.answers['calculate'] = {'answer': f"{query} = {result}"}
- except (TypeError, SyntaxError, ArithmeticError):
- pass
+ # Prevent the runtime from being longer than 50 ms
+ result = timeout_func(0.05, _eval_expr, query_py_formatted)
+ if result is None:
+ return True
+ result = str(result)
+
+ if result != query:
+ search.result_container.answers['calculate'] = {'answer': f"{query} = {result}"}
return True