Fix validation of target_exe blob name (#1371)

This commit is contained in:
Joe Ranweiler
2021-10-19 19:20:30 -07:00
committed by GitHub
parent deeb720c0f
commit 97a3a67b3e
2 changed files with 111 additions and 10 deletions

View File

@ -4,9 +4,8 @@
# Licensed under the MIT License. # Licensed under the MIT License.
import logging import logging
import ntpath
import os import os
import posixpath import pathlib
from typing import Dict, List, Optional from typing import Dict, List, Optional
from uuid import UUID from uuid import UUID
@ -105,14 +104,12 @@ def check_target_exe(config: TaskConfig, definition: TaskDefinition) -> None:
return return
# Azure Blob Store uses virtualized directory structures. As such, we need # User-submitted paths must be relative to the setup directory that contains them.
# the paths to already be canonicalized. As an example, accessing the blob # They also must be normalized, and exclude special filesystem path elements.
# store path "./foo" generates an exception, but "foo" and "foo/bar" do #
# not. # For example, accessing the blob store path "./foo" generates an exception, but
if ( # "foo" and "foo/bar" do not.
posixpath.relpath(config.task.target_exe) != config.task.target_exe if not is_valid_blob_name(config.task.target_exe):
or ntpath.relpath(config.task.target_exe) != config.task.target_exe
):
raise TaskConfigError("target_exe must be a canonicalized relative path") raise TaskConfigError("target_exe must be a canonicalized relative path")
container = [x for x in config.containers if x.type == ContainerType.setup][0] container = [x for x in config.containers if x.type == ContainerType.setup][0]
@ -124,6 +121,53 @@ def check_target_exe(config: TaskConfig, definition: TaskDefinition) -> None:
LOGGER.warning(err) LOGGER.warning(err)
# Azure Blob Storage uses a flat scheme, and has no true directory hierarchy. Forward
# slashes are used to delimit a _virtual_ directory structure.
def is_valid_blob_name(blob_name: str) -> bool:
# https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#blob-names
MIN_LENGTH = 1
MAX_LENGTH = 1024 # Inclusive
MAX_PATH_SEGMENTS = 254
length = len(blob_name)
# No leading/trailing whitespace.
if blob_name != blob_name.strip():
return False
if length < MIN_LENGTH:
return False
if length > MAX_LENGTH:
return False
path = pathlib.PurePosixPath(blob_name)
if len(path.parts) > MAX_PATH_SEGMENTS:
return False
# No path segment should end with a dot (`.`).
for part in path.parts:
if part.endswith("."):
return False
# Reject absolute paths to avoid confusion.
if path.is_absolute():
return False
# Reject paths with special relative filesystem entries.
if "." in path.parts:
return False
if ".." in path.parts:
return False
# Will not have a leading `.`, even if `blob_name` does.
normalized = path.as_posix()
return blob_name == normalized
def target_uses_input(config: TaskConfig) -> bool: def target_uses_input(config: TaskConfig) -> bool:
if config.task.target_options is not None: if config.task.target_options is not None:
for option in config.task.target_options: for option in config.task.target_options:

View File

@ -0,0 +1,57 @@
#!/usr/bin/env python
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
from typing import Tuple
import pytest
from __app__.onefuzzlib.tasks.config import is_valid_blob_name
BlobNameTestCase = Tuple[str, bool]
BLOB_NAME_TEST_CASES = [
# Valid
("fuzz.exe", True),
("bin/fuzz.exe", True),
("/".join("a" * 254), True),
("a" * 1024, True),
# Invalid (absolute)
("/fuzz.exe", False),
("/bin/fuzz.exe", False),
# Invalid (special dirs)
("./fuzz.exe", False),
("././fuzz.exe", False),
("./bin/fuzz.exe", False),
("./bin/./fuzz.exe", False),
("../fuzz.exe", False),
("../bin/fuzz.exe", False),
(".././fuzz.exe", False),
("../bin/./fuzz.exe", False),
# Out of Azure size bounds
("", False),
(" ", False),
("/".join("a" * 255), False),
("a" * 1025, False),
# Paths with invalid segments.
("a.", False),
("a..", False),
("a./b", False),
("a/b./c", False),
("a./", False),
("a../", False),
("a./b/", False),
("a/b./c/", False),
("a//", False),
]
@pytest.mark.parametrize("blob_name_test_case", BLOB_NAME_TEST_CASES)
def test_is_valid_blob_name(blob_name_test_case: BlobNameTestCase) -> None:
blob_name, expected = blob_name_test_case
is_valid = is_valid_blob_name(blob_name)
assert is_valid == expected