-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix: Enhancements Needed for Secure Tar Extraction (5560) #5690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -647,7 +647,10 @@ def _validate_source_directory(source_directory): | |
|
|
||
| # Check if the source path is under any sensitive directory | ||
| for sensitive_path in _SENSITIVE_SYSTEM_PATHS: | ||
aviruthen marked this conversation as resolved.
Show resolved
Hide resolved
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: try:
common = os.path.commonpath([abs_source, sensitive_path])
if abs_source != "/" and common == sensitive_path:
raise ValueError(...)
except ValueError:
# No common path (e.g., different drives on Windows) — not under sensitive path
passThis defensive pattern should be applied to all four locations where |
||
| if abs_source != "/" and abs_source.startswith(sensitive_path): | ||
| if abs_source != "/" and ( | ||
| os.path.commonpath([abs_source, sensitive_path]) | ||
| == sensitive_path | ||
| ): | ||
| raise ValueError( | ||
| f"source_directory cannot access sensitive system paths. " | ||
| f"Got: {source_directory} (resolved to {abs_source})" | ||
|
|
@@ -673,7 +676,10 @@ def _validate_dependency_path(dependency): | |
|
|
||
| # Check if the dependency path is under any sensitive directory | ||
| for sensitive_path in _SENSITIVE_SYSTEM_PATHS: | ||
aviruthen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if abs_dependency != "/" and abs_dependency.startswith(sensitive_path): | ||
| if abs_dependency != "/" and ( | ||
| os.path.commonpath([abs_dependency, sensitive_path]) | ||
| == sensitive_path | ||
| ): | ||
| raise ValueError( | ||
| f"dependency path cannot access sensitive system paths. " | ||
| f"Got: {dependency} (resolved to {abs_dependency})" | ||
|
|
@@ -686,10 +692,13 @@ def _create_or_update_code_dir( | |
| """Placeholder docstring""" | ||
| code_dir = os.path.join(model_dir, "code") | ||
| resolved_code_dir = _get_resolved_path(code_dir) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same |
||
|
|
||
| # Validate that code_dir does not resolve to a sensitive system path | ||
| for sensitive_path in _SENSITIVE_SYSTEM_PATHS: | ||
aviruthen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if resolved_code_dir != "/" and resolved_code_dir.startswith(sensitive_path): | ||
| if resolved_code_dir != "/" and ( | ||
| os.path.commonpath([resolved_code_dir, sensitive_path]) | ||
| == sensitive_path | ||
| ): | ||
| raise ValueError( | ||
| f"Invalid code_dir path: {code_dir} resolves to sensitive system path {resolved_code_dir}" | ||
| ) | ||
|
|
@@ -1688,7 +1697,8 @@ def _is_bad_path(path, base): | |
| bool: True if the path is not rooted under the base directory, False otherwise. | ||
| """ | ||
| # joinpath will ignore base if path is absolute | ||
| return not _get_resolved_path(joinpath(base, path)).startswith(base) | ||
| resolved = _get_resolved_path(joinpath(base, path)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same def _is_bad_path(path, base):
resolved = _get_resolved_path(joinpath(base, path))
try:
return os.path.commonpath([resolved, base]) != base
except ValueError:
return True |
||
| return os.path.commonpath([resolved, base]) != base | ||
|
|
||
|
|
||
| def _is_bad_link(info, base): | ||
|
|
@@ -1708,19 +1718,18 @@ def _is_bad_link(info, base): | |
| return _is_bad_path(info.linkname, base=tip) | ||
|
|
||
|
|
||
| def _get_safe_members(members): | ||
| def _get_safe_members(members, base): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Signature change to |
||
| """A generator that yields members that are safe to extract. | ||
|
|
||
| It filters out bad paths and bad links. | ||
|
|
||
| Args: | ||
| members (list): A list of members to check. | ||
| members (list): A list of TarInfo members to check. | ||
| base (str): The resolved base directory for extraction. | ||
|
|
||
| Yields: | ||
| tarfile.TarInfo: The tar file info. | ||
| """ | ||
| base = _get_resolved_path("") | ||
|
|
||
| for file_info in members: | ||
| if _is_bad_path(file_info.name, base): | ||
| logger.error("%s is blocked (illegal path)", file_info.name) | ||
|
|
@@ -1783,7 +1792,11 @@ def custom_extractall_tarfile(tar, extract_path): | |
| if hasattr(tarfile, "data_filter"): | ||
| tar.extractall(path=extract_path, filter="data") | ||
| else: | ||
| tar.extractall(path=extract_path, members=_get_safe_members(tar)) | ||
| base = _get_resolved_path(extract_path) | ||
| tar.extractall( | ||
| path=extract_path, | ||
| members=_get_safe_members(tar.getmembers(), base), | ||
| ) | ||
| # Re-validate extracted paths to catch symlink race conditions | ||
| _validate_extracted_paths(extract_path) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.