Add validation checks to prevent arbitrary code execution via stitching.
This commit is contained in:
parent
a632ddee92
commit
7569c7d6b2
5 changed files with 114 additions and 19 deletions
|
|
@ -14,6 +14,8 @@ import time
|
|||
|
||||
import labthings_fastapi as lt
|
||||
|
||||
from openflexure_microscope_server.utilities import make_path_safe
|
||||
|
||||
STITCHING_CMD = "openflexure-stitch"
|
||||
STITCHING_RESOLUTION = (820, 616)
|
||||
|
||||
|
|
@ -21,6 +23,10 @@ DEFAULT_OVERLAP = 0.1
|
|||
DEFAULT_RESIZE = 0.5
|
||||
|
||||
|
||||
class StitcherValidationError(RuntimeError):
|
||||
"""The stitcher received values that it deems unsafe to create a command from."""
|
||||
|
||||
|
||||
class BaseStitcher:
|
||||
"""A base stitching class for all stitchers. Don't initialise this directly."""
|
||||
|
||||
|
|
@ -36,24 +42,49 @@ class BaseStitcher:
|
|||
# Set minimum overlap to 90% of the scan overlap to catch only images
|
||||
# directly adjacent, not images with overlapping corners.
|
||||
self.images_dir = images_dir
|
||||
try:
|
||||
overlap = float(overlap)
|
||||
correlation_resize = float(correlation_resize)
|
||||
except ValueError as e:
|
||||
raise StitcherValidationError(
|
||||
"Stitching inputs overlap or correlation_resize were not floats as "
|
||||
"expected."
|
||||
) from e
|
||||
self.validate_path()
|
||||
|
||||
self.min_overlap = round(overlap * 0.9, 2)
|
||||
self.correlation_resize = correlation_resize
|
||||
self.correlation_resize = float(correlation_resize)
|
||||
self._mode = "all"
|
||||
self._extra_args = []
|
||||
|
||||
@property
|
||||
def command(self) -> list[str]:
|
||||
"""The command to run with subprocess.Popen."""
|
||||
# Revalidate for good measure,
|
||||
self.validate_path()
|
||||
# The command, and the mode
|
||||
initial_args = [STITCHING_CMD, "--stitching_mode", self._mode]
|
||||
# Use float() just to really ensure that anything input is a float
|
||||
setting_args = [
|
||||
"--minimum_overlap",
|
||||
f"{self.min_overlap}",
|
||||
f"{float(self.min_overlap)}",
|
||||
"--resize",
|
||||
f"{self.correlation_resize}",
|
||||
f"{float(self.correlation_resize)}",
|
||||
]
|
||||
return initial_args + self._extra_args + setting_args + [self.images_dir]
|
||||
|
||||
def validate_path(self):
|
||||
"""Check path is safe before making a command to run with subprocess.
|
||||
|
||||
This is essential for stopping arbitrary code execution.
|
||||
|
||||
:raises RuntimeError: if inputs are unsafe.
|
||||
"""
|
||||
if self.images_dir != make_path_safe(self.images_dir):
|
||||
raise StitcherValidationError(
|
||||
"Invalid directory path contains unsafe characters."
|
||||
)
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start stitching a stitching process.
|
||||
|
||||
|
|
@ -91,20 +122,6 @@ class PreviewStitcher(BaseStitcher):
|
|||
|
||||
self._mode = "preview_stitch"
|
||||
|
||||
@property
|
||||
def command(self) -> list[str]:
|
||||
"""The command to run with subprocess.Popen."""
|
||||
return [
|
||||
STITCHING_CMD,
|
||||
"--stitching_mode",
|
||||
"preview_stitch",
|
||||
"--minimum_overlap",
|
||||
f"{self.min_overlap}",
|
||||
"--resize",
|
||||
f"{self.correlation_resize}",
|
||||
self.images_dir,
|
||||
]
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start stitching a preview of the scan in a background subprocess.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue