Skip to content

Commit

Permalink
slice_ without eval
Browse files Browse the repository at this point in the history
  • Loading branch information
hyperrealist committed May 31, 2024
1 parent 9059fd0 commit c82e7f5
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 14 deletions.
106 changes: 106 additions & 0 deletions tiled/_tests/test_slicer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import pytest
from fastapi import Query

from ..server.dependencies import slice_

slice_test_data = [
"",
":",
"::" "0",
"0:",
"0::",
":0",
"::0",
"5:",
":10",
"::12",
"-1",
"-5:",
":-5",
"::-45",
"3:5",
"5:3",
"123::4",
"5::678",
":123:4",
":5:678",
",",
",,",
",:",
":,::",
",,:,::,,::,:,,::,",
"0,1,2",
"5:,:10,::-5",
"1:2:3,4:5:6,7:8:9",
"10::20,30::40,50::60",
"1 : 2",
"1:2, 3",
"1 ,2:3",
"1 , 2 , 3",
]

slice_typo_data = [
":::",
"1:2:3:4",
"1:2,3:4:5:6",
]

slice_malicious_data = [
"1:(2+3)",
"1**2",
"print('oh so innocent')",
"; print('oh so innocent')",
")\"; print('oh so innocent')",
"1:2)\"; print('oh so innocent')",
"1:2)\";print('oh_so_innocent')",
"import sys; sys.exit()",
"; import sys; sys.exit()",
"touch /tmp/x",
"rm -rf /tmp/*",
]


# this is the outgoing slice_ function from tiled.server.dependencies as is
def reference_slice_(
slice: str = Query(None, pattern="^[-0-9,:]*$"),
):
"Specify and parse a block index parameter."
import numpy

# IMPORTANT We are eval-ing a user-provider string here so we need to be
# very careful about locking down what can be in it. The regex above
# excludes any letters or operators, so it is not possible to execute
# functions or expensive arithmetic.
return tuple(
[
eval(f"numpy.s_[{dim!s}]", {"numpy": numpy})
for dim in (slice or "").split(",")
if dim
]
)


@pytest.mark.parametrize("slice", slice_test_data)
def test_slicer(slice: str):
"""
Test the slicer function
"""
assert slice_(slice) == reference_slice_(slice)


@pytest.mark.parametrize("slice", slice_typo_data)
def test_slicer_typo_data(slice: str):
"""
Test the slicer function with invalid input
"""
with pytest.raises(TypeError):
_ = slice_(slice)


@pytest.mark.parametrize("slice", slice_malicious_data)
def test_slicer_malicious_exec(slice: str):
"""
Test the slicer function with 'malicious' input
"""
with pytest.raises(ValueError):
_ = slice_(slice)
28 changes: 14 additions & 14 deletions tiled/server/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
from .core import NoEntry
from .utils import filter_for_access, record_timing

# saving slice() to rescue after using "slice" for FastAPI dependency injection of slice_(slice: str)
slice_func = slice


@lru_cache(1)
def get_query_registry():
Expand Down Expand Up @@ -160,19 +163,16 @@ def expected_shape(


def slice_(
slice: str = Query(None, pattern="^[-0-9,:]*$"),
slice: str | None = None,
):
"Specify and parse a block index parameter."
import numpy

# IMPORTANT We are eval-ing a user-provider string here so we need to be
# very careful about locking down what can be in it. The regex above
# excludes any letters or operators, so it is not possible to execute
# functions or expensive arithmetic.
return tuple(
[
eval(f"numpy.s_[{dim!s}]", {"numpy": numpy})
for dim in (slice or "").split(",")
if dim
]
)

def np_style_slicer(indices: tuple):
return indices[0] if len(indices) == 1 else slice_func(*indices)

def parse_slice_str(dim: str):
return np_style_slicer(
tuple(int(idx) if idx else None for idx in dim.split(":"))
)

return tuple(parse_slice_str(dim) for dim in (slice or "").split(",") if dim)

0 comments on commit c82e7f5

Please sign in to comment.