Skip to content

Reject SCP receive through pre-existing symlinks#1034

Open
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/f_4660
Open

Reject SCP receive through pre-existing symlinks#1034
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:masterfrom
yosuke-wolfssl:fix/f_4660

Conversation

@yosuke-wolfssl

@yosuke-wolfssl yosuke-wolfssl commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a path-traversal vulnerability in the default SCP receive callback wsScpRecvCallback().
On POSIX and Windows builds, an authenticated SCP peer could write outside the destination directory by uploading into a pre-existing symlink.

In the WOLFSSH_SCP_NEW_DIR handler, WMKDIR returning EEXIST was silently accepted and the subsequent WCHDIR followed a planted symlink, moving the working directory outside scpBasePath; every later WFOPEN then wrote to the escaped location.
A pre-existing file symlink was likewise followed by the WOLFSSH_SCP_NEW_FILE open. GetScpFileName rejects .. and / but offers no protection against valid-named symlinks, so a peer with a planted symlink (via SFTP SSH_FXP_SYMLINK, a shell session, or another upload mechanism) could write to arbitrary filesystem locations.
The WOLFSSL_NUCLEUS path was unaffected as it builds an absolute path and runs wolfSSH_CleanPath.

Addressed by f_4660.

Changes

  • Reject symlinked targets in the SCP receive path. Added a guard before WCHDIR(fileName) (directory case) and before WFOPEN(fileName, "wb") (file case) that refuses to follow a symlink, mirroring the symlink defense already used by SFTP. The guards compile out where WOLFSSH_HAVE_SYMLINK is undefined, so link-less embedded filesystems are unchanged.
  • Shared helper. Promoted the duplicated SFTP SFTP_IsSymlink check to a single wIsSymlink() in port.c (declared in port.h), and call it from both the SCP and SFTP path-confinement paths. The name follows the cross-platform w* convention in port.h (wStat, wMkDir, …); WS_* is reserved for the Windows-API emulation shims.
  • Build gate. wIsSymlink() lives in the always-compiled port.c, but its filesystem dependencies (WSTAT_T/WLSTAT/S_ISLNK on POSIX, WS_GetFileAttributesExA on Windows) and its only callers exist solely in the SFTP/SCP code. Gated it on (WOLFSSH_SFTP || WOLFSSH_SCP) in addition to WOLFSSH_HAVE_SYMLINK so a default ./configure build (no SFTP/SCP/SSHD) still compiles.
  • Test. Added test_ScpRecvCallback_SymlinkGuard to tests/unit.c, covering both the directory and file symlink cases plus positive controls for normal new directory/file creation.

Files

  • src/wolfscp.c — symlink guards in WOLFSSH_SCP_NEW_DIR / WOLFSSH_SCP_NEW_FILE
  • src/wolfsftp.c — use shared wIsSymlink() in GetAndCleanPath()
  • src/port.c, wolfssh/port.h — shared wIsSymlink() helper + feature gate
  • tests/unit.c — regression test
  • ChangeLog.md — vulnerability entry

Testing

  • make check passes for default, --enable-scp, --enable-sftp, and
    --enable-scp --enable-sftp builds.
  • New unit test passes with the fix and fails with the guards reverted
    (negative control confirms it catches the bug).
  • scripts/scp.test end-to-end passes (normal SCP dir/file uploads still work).
  • ASan + UBSan run of tests/unit.test is clean (no use-after-free, overflow,
    or runtime-error reports).
  • Default ./configure && make (no SFTP/SCP/SSHD) compiles, confirming the
    build gate.

@yosuke-wolfssl yosuke-wolfssl self-assigned this Jun 16, 2026
Copilot AI review requested due to automatic review settings June 16, 2026 01:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR mitigates an SCP receive path-traversal vulnerability where an authenticated peer could escape the configured destination directory by targeting pre-existing symlinks (directory or file) during uploads. It adds symlink rejection in the SCP receive callback and factors existing SFTP symlink detection into a shared helper for consistent confinement behavior.

Changes:

  • Add symlink guards to SCP receive path handling for both directory entry (NEW_DIR) and file creation (NEW_FILE).
  • Promote SFTP’s symlink detection into a shared wIsSymlink() helper (with feature/build gating) and reuse it in SFTP confinement checks.
  • Add a unit regression test covering both symlinked-directory and symlinked-file escape attempts.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/wolfscp.c Rejects SCP receive operations that would follow pre-existing symlinks when entering directories or opening files.
src/wolfsftp.c Replaces the local symlink check with shared wIsSymlink() for SFTP path confinement.
src/port.c Introduces shared cross-platform wIsSymlink() implementation (POSIX lstat / Windows reparse point attributes).
wolfssh/port.h Declares wIsSymlink() behind WOLFSSH_HAVE_SYMLINK and SFTP/SCP feature gates.
tests/unit.c Adds regression test ensuring SCP receive aborts when a planted symlink would escape the destination.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/unit.c Outdated

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #1034

Scan targets checked: wolfssh-bugs, wolfssh-src

No new issues found in the changed files. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants