Skip to content

_vrt.py realpath() canonicalises but does not block path traversal #1671

@brendancol

Description

@brendancol

Describe the bug

xrspatial/geotiff/_vrt.py:184-187:

```python
if is_relative and not os.path.isabs(filename):
filename = os.path.join(vrt_dir, filename)

Canonicalize to prevent path traversal (e.g. ../)

filename = os.path.realpath(filename)
```

The comment promises path-traversal prevention. realpath only resolves .. and symlinks. The resolved result can still point anywhere on disk: <vrt_dir>/../../etc/passwd resolves to /etc/passwd and the source is then opened by read_to_array. A symlink under vrt_dir to a sensitive file behaves the same way.

For trusted VRTs this is fine. For VRTs accepted from user input (uploads, untrusted mounts) it is a path-traversal primitive.

Expected behavior

When vrt_dir is the trusted root for relative sources, the resolved path must live under realpath(vrt_dir). Absolute paths declared inside the VRT either error out or pass through an explicit opt-in allowlist.

Suggested fix

After realpath, assert the result is under realpath(vrt_dir) using os.path.commonpath or a startswith(root + os.sep) check. Add an environment variable or kwarg for users who legitimately point at sibling directories. Document the new behavior in the read_vrt docstring.

Tests:

  • A VRT in /tmp/vrt_a/foo.vrt referencing ../vrt_b/data.tif raises ValueError.
  • The same VRT with an explicit allowlist that names /tmp/vrt_b succeeds.
  • A VRT pointing at a symlink that resolves outside vrt_dir raises ValueError.

Additional context

Reported during a code review of the geotiff module. The comment on line 186 reads as a security claim that the code does not back up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions