mirror of https://github.com/wolfSSL/wolfssh.git
wolfsshd: open PID file with O_NOFOLLOW and a fixed mode
parent
a10e6e5c0c
commit
cc5311a829
|
|
@ -29,6 +29,18 @@
|
|||
#endif
|
||||
|
||||
#ifdef WOLFSSH_SSHD
|
||||
|
||||
/* Define POSIX feature-test macros before any system header (as auth.c does) so
|
||||
* fdopen/ftruncate/lstat/S_ISLNK are declared under strict -std= builds. */
|
||||
#ifdef __linux__
|
||||
#ifndef _XOPEN_SOURCE
|
||||
#define _XOPEN_SOURCE
|
||||
#endif
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* functions for parsing out options from a config file and for handling loading
|
||||
* key/certs using the env. filesystem */
|
||||
|
||||
|
|
@ -51,10 +63,14 @@
|
|||
|
||||
#include "configuration.h"
|
||||
|
||||
#ifndef WIN32
|
||||
#ifndef _WIN32
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32
|
||||
#include <process.h>
|
||||
#endif
|
||||
#ifdef HAVE_LIMITS_H
|
||||
|
|
@ -1814,18 +1830,113 @@ void wolfSSHD_ConfigSavePID(const WOLFSSHD_CONFIG* conf)
|
|||
{
|
||||
FILE* f;
|
||||
char buf[12]; /* large enough to hold 'int' type with null terminator */
|
||||
word32 pidLen;
|
||||
int writeOk;
|
||||
#ifndef _WIN32
|
||||
int fd;
|
||||
int ok = 1;
|
||||
int created = 1; /* whether this call created the PID file */
|
||||
struct stat st;
|
||||
#endif
|
||||
|
||||
if (conf->pidFile != NULL) {
|
||||
WMEMSET(buf, 0, sizeof(buf));
|
||||
if (WFOPEN(NULL, &f, conf->pidFile, "wb") == 0) {
|
||||
#ifndef WIN32
|
||||
WSNPRINTF(buf, sizeof(buf), "%d", getpid());
|
||||
#else
|
||||
WSNPRINTF(buf, sizeof(buf), "%d", _getpid());
|
||||
#endif
|
||||
WFWRITE(NULL, buf, 1, WSTRLEN(buf), f);
|
||||
WFCLOSE(NULL, f);
|
||||
#ifndef _WIN32
|
||||
/* raw open, not WFOPEN: the O_NOFOLLOW hardening needs a real fd */
|
||||
#if defined(WOLFSSH_HAVE_SYMLINK) && WOLFSSH_O_NOFOLLOW == 0
|
||||
/* no O_NOFOLLOW here, so pre-check with a racy best-effort lstat */
|
||||
if (lstat(conf->pidFile, &st) == 0 && S_ISLNK(st.st_mode)) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Refusing symlinked PID file %s", conf->pidFile);
|
||||
ok = 0;
|
||||
}
|
||||
#endif
|
||||
if (ok) {
|
||||
/* O_EXCL first so 'created' distinguishes creation from reuse; on
|
||||
* EEXIST reopen (O_NOFOLLOW still guards a symlink). No O_TRUNC and
|
||||
* O_NONBLOCK so a planted FIFO fails fast, nothing clobbered. */
|
||||
fd = open(conf->pidFile,
|
||||
O_WRONLY | O_CREAT | O_EXCL | O_NONBLOCK |
|
||||
WOLFSSH_O_NOFOLLOW, 0644);
|
||||
if (fd < 0 && errno == EEXIST) {
|
||||
created = 0;
|
||||
fd = open(conf->pidFile,
|
||||
O_WRONLY | O_NONBLOCK | WOLFSSH_O_NOFOLLOW, 0);
|
||||
}
|
||||
if (fd < 0) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Unable to open PID file %s", conf->pidFile);
|
||||
}
|
||||
/* O_NOFOLLOW stops a symlink but not a hard link; demand a
|
||||
* single-link regular file owned by us or by root. */
|
||||
else if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode) ||
|
||||
st.st_nlink != 1 ||
|
||||
(st.st_uid != geteuid() && st.st_uid != 0)) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Refusing unsafe PID file %s", conf->pidFile);
|
||||
close(fd);
|
||||
}
|
||||
/* open()'s 0644 applies only on creation, so an existing
|
||||
* world-writable PID file needs its mode reset here. */
|
||||
else if (fchmod(fd, 0644) != 0) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Unable to set mode on PID file %s", conf->pidFile);
|
||||
close(fd);
|
||||
/* remove only a file we just made, never a valid existing one */
|
||||
if (created) {
|
||||
unlink(conf->pidFile);
|
||||
}
|
||||
}
|
||||
else if (ftruncate(fd, 0) != 0) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Unable to truncate PID file %s", conf->pidFile);
|
||||
close(fd);
|
||||
if (created) {
|
||||
unlink(conf->pidFile);
|
||||
}
|
||||
}
|
||||
else {
|
||||
f = fdopen(fd, "wb");
|
||||
if (f == NULL) {
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Unable to open PID file stream %s",
|
||||
conf->pidFile);
|
||||
close(fd);
|
||||
/* drop the empty leftover, a reader must not see no PID */
|
||||
unlink(conf->pidFile);
|
||||
}
|
||||
else {
|
||||
WSNPRINTF(buf, sizeof(buf), "%d", getpid());
|
||||
pidLen = (word32)WSTRLEN(buf);
|
||||
writeOk = ((word32)WFWRITE(NULL, buf, 1, pidLen, f) == pidLen);
|
||||
if (WFCLOSE(NULL, f) != 0) {
|
||||
writeOk = 0;
|
||||
}
|
||||
if (!writeOk) {
|
||||
/* a reader must not parse a truncated PID */
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Failed to write PID file %s", conf->pidFile);
|
||||
unlink(conf->pidFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (WFOPEN(NULL, &f, conf->pidFile, "wb") == 0) {
|
||||
WSNPRINTF(buf, sizeof(buf), "%d", _getpid());
|
||||
pidLen = (word32)WSTRLEN(buf);
|
||||
writeOk = ((word32)WFWRITE(NULL, buf, 1, pidLen, f) == pidLen);
|
||||
if (WFCLOSE(NULL, f) != 0) {
|
||||
writeOk = 0;
|
||||
}
|
||||
if (!writeOk) {
|
||||
/* a reader must not parse a truncated PID */
|
||||
wolfSSH_Log(WS_LOG_ERROR,
|
||||
"[SSHD] Failed to write PID file %s", conf->pidFile);
|
||||
WREMOVE(NULL, conf->pidFile);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/resource.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
|
@ -2294,6 +2296,340 @@ static int test_OpenSecureFile(void)
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* write a config file whose PidFile is 'pidTarget' */
|
||||
static int pidConfWrite(const char* confPath, const char* pidTarget)
|
||||
{
|
||||
FILE* f;
|
||||
|
||||
f = fopen(confPath, "w");
|
||||
if (f == NULL) {
|
||||
return WS_FATAL_ERROR;
|
||||
}
|
||||
fprintf(f, "PidFile %s\n", pidTarget);
|
||||
fclose(f);
|
||||
return WS_SUCCESS;
|
||||
}
|
||||
|
||||
/* load 'confPath' and run wolfSSHD_ConfigSavePID under umask(0), restoring the
|
||||
* umask afterward so later tests are unaffected */
|
||||
static int pidSaveRun(const char* confPath)
|
||||
{
|
||||
int ret;
|
||||
mode_t old;
|
||||
WOLFSSHD_CONFIG* cfg;
|
||||
|
||||
cfg = wolfSSHD_ConfigNew(NULL);
|
||||
if (cfg == NULL) {
|
||||
return WS_MEMORY_E;
|
||||
}
|
||||
ret = wolfSSHD_ConfigLoad(cfg, confPath);
|
||||
if (ret == WS_SUCCESS) {
|
||||
/* deliberate worst case: prove the explicit 0644 mode holds even when
|
||||
* the umask would not mask any bits */
|
||||
old = umask(0);
|
||||
wolfSSHD_ConfigSavePID(cfg);
|
||||
umask(old);
|
||||
}
|
||||
wolfSSHD_ConfigFree(cfg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pidSave(const char* confPath, const char* pidTarget)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = pidConfWrite(confPath, pidTarget);
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = pidSaveRun(confPath);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* wolfSSHD_ConfigSavePID must refuse a symlink at the PID path so a planted
|
||||
* link cannot truncate another file, and must leave the PID file without group
|
||||
* or world write even under a permissive umask, whether it creates the file or
|
||||
* reuses an existing one. */
|
||||
static int test_ConfigSavePID(void)
|
||||
{
|
||||
int ret = WS_SUCCESS;
|
||||
int rd;
|
||||
long pid = -1;
|
||||
char base[] = "/tmp/wolfsshd_pidXXXXXX";
|
||||
char conf[80] = "";
|
||||
char pidPath[96] = "";
|
||||
char victim[96] = "";
|
||||
char linkPath[96] = "";
|
||||
char hvictim[96] = "";
|
||||
char hlink[96] = "";
|
||||
char fifo[96] = "";
|
||||
char stale[96] = "";
|
||||
char foreign[96] = "";
|
||||
const char* secret = "VICTIM-CONTENTS\n";
|
||||
FILE* f = NULL;
|
||||
struct stat st;
|
||||
char rbuf[64];
|
||||
#ifdef RLIMIT_FSIZE
|
||||
char failPath[96] = "";
|
||||
struct rlimit rlOld;
|
||||
struct rlimit rlNew;
|
||||
void (*prevXfsz)(int);
|
||||
#endif
|
||||
|
||||
if (mkdtemp(base) == NULL) {
|
||||
Log(" mkdtemp failed.\n");
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
|
||||
if (ret == WS_SUCCESS) {
|
||||
snprintf(conf, sizeof(conf), "%s/sshd_config", base);
|
||||
snprintf(pidPath, sizeof(pidPath), "%s/wolfsshd.pid", base);
|
||||
snprintf(victim, sizeof(victim), "%s/victim", base);
|
||||
snprintf(linkPath, sizeof(linkPath), "%s/link.pid", base);
|
||||
snprintf(hvictim, sizeof(hvictim), "%s/hvictim", base);
|
||||
snprintf(hlink, sizeof(hlink), "%s/hlink.pid", base);
|
||||
snprintf(fifo, sizeof(fifo), "%s/fifo.pid", base);
|
||||
snprintf(stale, sizeof(stale), "%s/stale.pid", base);
|
||||
snprintf(foreign, sizeof(foreign), "%s/foreign.pid", base);
|
||||
#ifdef RLIMIT_FSIZE
|
||||
snprintf(failPath, sizeof(failPath), "%s/fail.pid", base);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Scenario 1: a normal path is written with our PID and is not group or
|
||||
* world writable despite umask(0). */
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = pidSave(conf, pidPath);
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
if (stat(pidPath, &st) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
else {
|
||||
f = fopen(pidPath, "r");
|
||||
rd = (f != NULL) ? fscanf(f, "%ld", &pid) : 0;
|
||||
if (f != NULL) {
|
||||
fclose(f);
|
||||
}
|
||||
ret = smExpect("normal PID file written with our PID",
|
||||
(rd == 1 && pid == (long)getpid()) ? WS_SUCCESS
|
||||
: WS_FATAL_ERROR, 1);
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = smExpect("PID file not group or world writable",
|
||||
(st.st_mode & (S_IWGRP | S_IWOTH)) ? WS_FATAL_ERROR
|
||||
: WS_SUCCESS, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Scenario 2: a symlink at the PID path is refused, link target untouched.
|
||||
* The common build exercises the atomic O_NOFOLLOW path; the lstat fallback
|
||||
* needs a symlink-capable platform without O_NOFOLLOW. */
|
||||
if (ret == WS_SUCCESS) {
|
||||
f = fopen(victim, "w");
|
||||
if (f == NULL) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
else {
|
||||
fputs(secret, f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
if (ret == WS_SUCCESS && symlink(victim, linkPath) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = pidSave(conf, linkPath);
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
f = fopen(victim, "r");
|
||||
WMEMSET(rbuf, 0, sizeof(rbuf));
|
||||
rd = (f != NULL) ? (int)fread(rbuf, 1, sizeof(rbuf) - 1, f) : -1;
|
||||
if (f != NULL) {
|
||||
fclose(f);
|
||||
}
|
||||
(void)rd;
|
||||
/* A WOLFSSH_NO_SYMLINK_CHECK build does no symlink check by design, so
|
||||
* only assert the target survived when the check is compiled in. */
|
||||
#ifdef WOLFSSH_HAVE_SYMLINK
|
||||
ret = smExpect("symlinked PID path not followed, target intact",
|
||||
(WSTRCMP(rbuf, secret) == 0) ? WS_SUCCESS : WS_FATAL_ERROR, 1);
|
||||
#else
|
||||
(void)rbuf;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Scenario 3: a hard link at the PID path is refused (O_NOFOLLOW stops a
|
||||
* symlink but not a hard link), leaving the link target untouched. The
|
||||
* st_nlink check that enforces this is unconditional, so this always runs. */
|
||||
if (ret == WS_SUCCESS) {
|
||||
f = fopen(hvictim, "w");
|
||||
if (f == NULL) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
else {
|
||||
fputs(secret, f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
if (ret == WS_SUCCESS && link(hvictim, hlink) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = pidSave(conf, hlink);
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
f = fopen(hvictim, "r");
|
||||
WMEMSET(rbuf, 0, sizeof(rbuf));
|
||||
rd = (f != NULL) ? (int)fread(rbuf, 1, sizeof(rbuf) - 1, f) : -1;
|
||||
if (f != NULL) {
|
||||
fclose(f);
|
||||
}
|
||||
(void)rd;
|
||||
ret = smExpect("hard-linked PID path refused, target intact",
|
||||
(WSTRCMP(rbuf, secret) == 0) ? WS_SUCCESS : WS_FATAL_ERROR, 1);
|
||||
}
|
||||
|
||||
/* Scenario 4: a FIFO at the PID path is rejected (O_NONBLOCK fast-fails the
|
||||
* open and the S_ISREG check refuses it), and the FIFO is left in place
|
||||
* rather than replaced by a regular PID file. */
|
||||
if (ret == WS_SUCCESS && mkfifo(fifo, 0600) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = pidSave(conf, fifo);
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = smExpect("FIFO PID path refused, still a FIFO",
|
||||
(lstat(fifo, &st) == 0 && S_ISFIFO(st.st_mode)) ? WS_SUCCESS
|
||||
: WS_FATAL_ERROR, 1);
|
||||
}
|
||||
|
||||
/* Scenario 5: an already existing PID file is reused, but its mode is reset
|
||||
* so a world-writable file left behind by an earlier run does not stay
|
||||
* world writable. The mode passed to open() applies only on creation. */
|
||||
if (ret == WS_SUCCESS) {
|
||||
f = fopen(stale, "w");
|
||||
if (f == NULL) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
else {
|
||||
fputs(secret, f);
|
||||
fclose(f);
|
||||
if (chmod(stale, 0666) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = pidSave(conf, stale);
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
pid = -1;
|
||||
if (stat(stale, &st) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
else {
|
||||
f = fopen(stale, "r");
|
||||
rd = (f != NULL) ? fscanf(f, "%ld", &pid) : 0;
|
||||
if (f != NULL) {
|
||||
fclose(f);
|
||||
}
|
||||
ret = smExpect("existing PID file rewritten with our PID",
|
||||
(rd == 1 && pid == (long)getpid()) ? WS_SUCCESS
|
||||
: WS_FATAL_ERROR, 1);
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = smExpect("existing PID file mode reset, not world "
|
||||
"writable",
|
||||
(st.st_mode & (S_IWGRP | S_IWOTH)) ? WS_FATAL_ERROR
|
||||
: WS_SUCCESS, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Scenario 6: a PID file owned by another user is refused instead of being
|
||||
* truncated and adopted. Handing a file to another uid needs root, so this
|
||||
* only runs when the test is executed as root. */
|
||||
if (ret == WS_SUCCESS && geteuid() == 0) {
|
||||
f = fopen(foreign, "w");
|
||||
if (f == NULL) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
else {
|
||||
fputs(secret, f);
|
||||
fclose(f);
|
||||
if (chown(foreign, 1, 1) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = pidSave(conf, foreign);
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
f = fopen(foreign, "r");
|
||||
WMEMSET(rbuf, 0, sizeof(rbuf));
|
||||
rd = (f != NULL) ? (int)fread(rbuf, 1, sizeof(rbuf) - 1, f) : -1;
|
||||
if (f != NULL) {
|
||||
fclose(f);
|
||||
}
|
||||
(void)rd;
|
||||
ret = smExpect("PID file owned by another user refused, intact",
|
||||
(WSTRCMP(rbuf, secret) == 0) ? WS_SUCCESS : WS_FATAL_ERROR, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef RLIMIT_FSIZE
|
||||
/* Scenario 7: a PID file whose write fails is removed rather than left
|
||||
* behind empty. A zero RLIMIT_FSIZE makes the flush at close fail, which is
|
||||
* the recovery path taken by a short or unflushed write. */
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = pidConfWrite(conf, failPath);
|
||||
}
|
||||
if (ret == WS_SUCCESS && getrlimit(RLIMIT_FSIZE, &rlOld) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
/* exceeding the limit raises SIGXFSZ, which would kill the test */
|
||||
prevXfsz = signal(SIGXFSZ, SIG_IGN);
|
||||
rlNew = rlOld;
|
||||
rlNew.rlim_cur = 0;
|
||||
/* flush first: while the cap is set, a write to a redirected
|
||||
* stdout/stderr would silently fail (SIGXFSZ is ignored) */
|
||||
fflush(stdout);
|
||||
if (setrlimit(RLIMIT_FSIZE, &rlNew) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
else {
|
||||
ret = pidSaveRun(conf);
|
||||
if (setrlimit(RLIMIT_FSIZE, &rlOld) != 0) {
|
||||
ret = WS_FATAL_ERROR;
|
||||
}
|
||||
}
|
||||
signal(SIGXFSZ, prevXfsz);
|
||||
}
|
||||
if (ret == WS_SUCCESS) {
|
||||
ret = smExpect("PID file removed when the write fails",
|
||||
(stat(failPath, &st) != 0) ? WS_SUCCESS : WS_FATAL_ERROR, 1);
|
||||
}
|
||||
#endif /* RLIMIT_FSIZE */
|
||||
|
||||
/* cleanup */
|
||||
unlink(fifo);
|
||||
unlink(linkPath);
|
||||
unlink(victim);
|
||||
unlink(hlink);
|
||||
unlink(hvictim);
|
||||
unlink(stale);
|
||||
unlink(foreign);
|
||||
#ifdef RLIMIT_FSIZE
|
||||
unlink(failPath);
|
||||
#endif
|
||||
unlink(pidPath);
|
||||
unlink(conf);
|
||||
rmdir(base);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* !_WIN32 */
|
||||
|
||||
/* Verify AuthorizedKeysFile token substitution so an absolute pattern with %u
|
||||
|
|
@ -2443,6 +2779,7 @@ const TEST_CASE testCases[] = {
|
|||
TEST_DECL(test_ConfigFree),
|
||||
#ifndef _WIN32
|
||||
TEST_DECL(test_OpenSecureFile),
|
||||
TEST_DECL(test_ConfigSavePID),
|
||||
#endif
|
||||
#ifdef WOLFSSL_BASE64_ENCODE
|
||||
TEST_DECL(test_CheckAuthKeysLine),
|
||||
|
|
|
|||
|
|
@ -1584,20 +1584,15 @@ extern "C" {
|
|||
#define WOLFSSH_HAVE_SYMLINK
|
||||
#endif
|
||||
|
||||
/* 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 and SCP code, so
|
||||
* gate it on those features in addition to the platform capability. Shared by
|
||||
* the SFTP path-confinement guard and the SCP send guards. */
|
||||
#if defined(WOLFSSH_HAVE_SYMLINK) && \
|
||||
(defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP))
|
||||
WOLFSSH_LOCAL int wIsSymlink(const char* path);
|
||||
|
||||
/* Open flag that refuses to follow a final-component symbolic link, so the
|
||||
* open is atomic against a swapped link. Maps to the platform primitive
|
||||
* where available (POSIX O_NOFOLLOW) and to 0 elsewhere (Windows and any
|
||||
* filesystem lacking it), where wFopenNoFollow falls back to a wIsSymlink
|
||||
* check before the open. */
|
||||
/* No-follow open flags, defined for every filesystem that can store a symlink
|
||||
* (POSIX and Windows) so the SFTP, SCP, and SSHD code share one definition. */
|
||||
#ifdef WOLFSSH_HAVE_SYMLINK
|
||||
#ifndef USE_WINDOWS_API
|
||||
#include <fcntl.h> /* supplies O_NOFOLLOW/O_DIRECTORY on POSIX */
|
||||
#endif
|
||||
/* Open flag refusing to follow a final-component symlink, so the open is
|
||||
* atomic against a swapped link. 0 where unavailable (Windows, link-less
|
||||
* filesystems), where the caller falls back to a wIsSymlink check. */
|
||||
#ifdef O_NOFOLLOW
|
||||
#define WOLFSSH_O_NOFOLLOW O_NOFOLLOW
|
||||
#else
|
||||
|
|
@ -1610,6 +1605,22 @@ extern "C" {
|
|||
#else
|
||||
#define WOLFSSH_O_DIRECTORY 0
|
||||
#endif
|
||||
#endif /* WOLFSSH_HAVE_SYMLINK */
|
||||
|
||||
/* Catch-all so callers can use WOLFSSH_O_NOFOLLOW unconditionally; resolves to
|
||||
* 0 when symlinks are not a concern (no-symlink filesystems, opt-out builds). */
|
||||
#ifndef WOLFSSH_O_NOFOLLOW
|
||||
#define WOLFSSH_O_NOFOLLOW 0
|
||||
#endif
|
||||
|
||||
/* 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 and SCP code, so
|
||||
* gate it on those features in addition to the platform capability. Shared by
|
||||
* the SFTP path-confinement guard and the SCP send guards. */
|
||||
#if defined(WOLFSSH_HAVE_SYMLINK) && \
|
||||
(defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP))
|
||||
WOLFSSH_LOCAL int wIsSymlink(const char* path);
|
||||
|
||||
/* Open a file for reading without following a final-component symlink.
|
||||
* Returns 0 on success, non-zero on failure, matching the wfopen
|
||||
|
|
|
|||
Loading…
Reference in New Issue