Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/DIRAC/Core/Workflow/Utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
import re
from DIRAC.Core.Utilities.SaferEval import saferEval

# Non-string workflow parameters (lists, dicts, ...) are evaluated from their repr()
# after variable substitution. Legitimate values are KB-scale and routinely exceed the
# saferEval default cap, so that default is too small here. We still bound the size as
# defence-in-depth against pathological/malicious input: literal_eval blocks code
# execution regardless of content, and this ceiling limits its object-allocation blow-up.
# Legitimate workflow values never approach it, so hitting it means the input is
# genuinely anomalous and should be rejected.
MAX_PARAMETER_VALUE_LEN = 1024 * 1024 # 1 MiB


def getSubstitute(param, skip_list=[]):
"""Get the variable name to which the given parameter is referring"""
Expand All @@ -24,7 +33,7 @@ def substitute(param, variable, value):
tmp_string = str(param).replace("@{" + variable + "}", value)
if isinstance(param, str):
return tmp_string
return saferEval(tmp_string)
return saferEval(tmp_string, max_len=MAX_PARAMETER_VALUE_LEN)


def resolveVariables(varDict):
Expand Down
12 changes: 11 additions & 1 deletion src/DIRAC/Core/Workflow/WorkflowReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
from DIRAC.Core.Workflow.Workflow import Workflow
from DIRAC.Core.Utilities.SaferEval import saferEval

# Non-string workflow parameters (lists, dicts, ...) are serialised to XML as the
# repr() of their value and read back by evaluating that string. Legitimate values
# (e.g. a listoutput or a BKProcessingPass dict) are KB-scale and routinely exceed
# the saferEval default cap, so that default is too small here. We still bound the
# size as defence-in-depth against pathological/malicious input: literal_eval blocks
# code execution regardless of content, and this ceiling limits its object-allocation
# blow-up. Legitimate workflow values never approach it, so hitting it means the input
# is genuinely anomalous and should be rejected.
MAX_PARAMETER_VALUE_LEN = 1024 * 1024 # 1 MiB


class WorkflowXMLHandler(ContentHandler):
def __init__(self, new_wf=None):
Expand Down Expand Up @@ -113,7 +123,7 @@ def endElement(self, name):
if self.stack[-1].isTypeString():
self.stack[-1].setValue(ch)
else:
self.stack[-1].setValue(saferEval(ch))
self.stack[-1].setValue(saferEval(ch, max_len=MAX_PARAMETER_VALUE_LEN))

# objects
elif name == "Workflow":
Expand Down
22 changes: 22 additions & 0 deletions src/DIRAC/Core/Workflow/test/Test_Utility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Regression tests for workflow Utility.substitute.

When a non-string workflow parameter (e.g. a list) contains ``@{var}`` references,
``substitute`` rebuilds the object by evaluating the substituted repr() string.
Such values can legitimately exceed the ``saferEval`` default length cap.

Regression: replacing ``eval`` with ``saferEval`` introduced a hard 2048-byte
limit, so substituting into a large non-string parameter failed with
``ValueError: Object string is too long (>2048 bytes)``.
"""

from DIRAC.Core.Workflow.Utility import substitute


def test_substitute_large_non_string_value():
# A non-string (list) parameter whose repr() exceeds the 2048-byte default cap.
param = [f"@{{PREFIX}}_{i:04d}" for i in range(200)]
assert len(str(param)) > 2048

result = substitute(param, "PREFIX", "LFN:/lhcb/data/2026")

assert result == [f"LFN:/lhcb/data/2026_{i:04d}" for i in range(200)]
28 changes: 28 additions & 0 deletions src/DIRAC/Core/Workflow/test/Test_WorkflowReader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Regression tests for WorkflowReader parsing of workflow XML.

Non-string workflow parameters (e.g. a ``list`` or ``dict``) are serialised to
XML as ``repr()`` of their value and read back by evaluating that string. Such
values can legitimately exceed the ``saferEval`` default length cap, so the
reader must not impose it.

Regression: replacing ``eval`` with ``saferEval`` introduced a hard 2048-byte
limit, so parsing a workflow with a large non-string parameter failed with
``ValueError: Object string is too long (>2048 bytes)``.
"""

from DIRAC.Core.Workflow.Parameter import Parameter
from DIRAC.Core.Workflow.Workflow import Workflow, fromXMLString


def test_round_trip_large_list_parameter():
# A list value whose repr() comfortably exceeds the 2048-byte default cap.
big_list = [f"LFN:/lhcb/data/2026/RAW/file_{i:05d}.raw" for i in range(300)]
assert len(str(big_list)) > 2048

wf = Workflow()
wf.setName("BigParamWF")
wf.addParameter(Parameter("InputDataList", big_list, "list"))

parsed = fromXMLString(wf.toXML())

assert parsed.findParameter("InputDataList").getValue() == big_list
Loading