Skip to content

Commit e9a0f08

Browse files
committed
Assert the path is inside OUTPUT dir before calling shutil.move
1 parent 515b5e4 commit e9a0f08

File tree

2 files changed

+20
-11
lines changed

2 files changed

+20
-11
lines changed

readthedocs/core/utils/filesystem.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,28 @@
2525

2626

2727
def assert_path_is_inside_docroot(path):
28+
"""Assert that the given path is inside the DOCROOT directory."""
29+
assert_path_is_inside_expected_path(path, Path(settings.DOCROOT).absolute())
30+
31+
32+
def assert_path_is_inside_expected_path(path, expected_path):
2833
"""
29-
Assert that the given path is inside the DOCROOT directory.
34+
Assert that the given path is inside the expected path directory.
3035
3136
Symlinks are resolved before checking, a SuspiciousFileOperation exception
32-
will be raised if the path is outside the DOCROOT.
37+
will be raised if the path is outside the expected path.
3338
3439
.. warning::
3540
3641
This operation isn't safe to TocTou (Time-of-check to Time-of-use) attacks.
3742
Users shouldn't be able to change files while this operation is done.
3843
"""
3944
resolved_path = path.absolute().resolve()
40-
docroot = Path(settings.DOCROOT).absolute()
41-
if not path.is_relative_to(docroot):
45+
if not path.is_relative_to(expected_path):
4246
log.error(
43-
"Suspicious operation outside the docroot directory.",
47+
"Suspicious operation outside the expected path directory.",
4448
path_resolved=str(resolved_path),
49+
expected_path=str(expected_path),
4550
)
4651
raise SuspiciousFileOperation(path)
4752

readthedocs/projects/tasks/builds.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import socket
1111
import subprocess
1212
from dataclasses import dataclass, field
13+
from pathlib import Path
1314

1415
import structlog
1516
from celery import Task
@@ -41,6 +42,7 @@
4142
from readthedocs.builds.utils import memcache_lock
4243
from readthedocs.config.config import BuildConfigV2
4344
from readthedocs.config.exceptions import ConfigError
45+
from readthedocs.core.utils.filesystem import assert_path_is_inside_expected_path
4446
from readthedocs.doc_builder.director import BuildDirector
4547
from readthedocs.doc_builder.environments import (
4648
DockerBuildEnvironment,
@@ -630,13 +632,15 @@ def get_valid_artifact_types(self):
630632

631633
# Rename file as "<project_slug>-<version_slug>.<artifact_type>",
632634
# which is the filename that Proxito serves for offline formats.
633-
filename = list_dir[0]
634-
_, extension = filename.rsplit(".")
635+
filename = os.path.join(artifact_directory, list_dir[0])
636+
_, extension = filename.rsplit(".", maxsplit=1)
637+
output = os.path.join(
638+
self.data.project.checkout_path(self.data.version.slug),
639+
"_readthedocs/",
640+
)
641+
assert_path_is_inside_expected_path(Path(filename), Path(output))
635642
shutil.move(
636-
os.path.join(
637-
artifact_directory,
638-
list_dir[0],
639-
),
643+
filename,
640644
os.path.join(
641645
artifact_directory,
642646
f"{self.data.project.slug}.{extension}",

0 commit comments

Comments
 (0)