Skip to content

Render driver exposes restrictive file content before chmod and updates non-atomically #171

Description

@cassi-volkova

Summary

Render.dump_to_dp() writes file content before applying the requested mode and ownership. With the common process umask 0022, a render requested as 0600 or 0640 is initially created as 0644 and can be observed by unrelated local users before chmod runs.

Render.update_on_dp() also removes the current file before recreating it, so readers can observe a missing, partial, or broadly readable replacement. A process interruption can leave the destination truncated or with unintended permissions.

Minimal reproduction

On a host with umask 0022:

  1. Create a render containing a non-production placeholder such as example_secret=not-a-real-secret.
  2. Set the requested mode to 0600.
  3. Pause or instrument execution immediately after f.write(self.content) and before os.chmod(self.path, mode).
  4. Inspect the destination mode and read it from another unprivileged local account.
  5. For update behavior, observe the path between delete_from_dp() and dump_to_dp().

The current implementation performs these operations in this order:

with open(self.path, "w") as f:
    f.write(self.content)

os.chmod(self.path, mode)
os.chown(self.path, owner, group)

and updates by deleting the destination before calling dump_to_dp().

Expected result

Rendered content is never present with permissions broader than the requested mode, and updates become visible atomically as a complete file.

Actual result

The destination can temporarily be 0644 before the requested restrictive mode is applied. Updates also introduce an unlink/recreate window and expose partially written state.

Security impact

Render resources can contain credentials or other sensitive configuration. A local unprivileged process can race the write and read content that should only be available to the configured owner or group. Non-atomic replacement also creates integrity and availability risk for consumers reading the file concurrently.

Suggested fix

Resolve the requested uid, gid, and mode before writing. Create a unique temporary file in the destination directory with restrictive permissions, apply final ownership and mode before writing sensitive content, write and flush the complete content, fsync it, and replace the destination with os.replace(). Then fsync the parent directory and invoke on_change. Do not unlink the destination first.

Add regression coverage that verifies:

  • content is never written to a file with broader permissions than requested;
  • the old complete file remains visible until atomic replacement;
  • failure before replacement leaves the old destination intact;
  • on_change runs only after the replacement succeeds.

Confirmed on current master at 1f793118fad2f171b34412e59b720bdad6197290.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions