4646
4747#include <signal.h>
4848
49+ #ifndef _WIN32
50+ /* Used by getBufferFromFile() to load security-critical files without
51+ * following an attacker-supplied symlink or trusting an unsafe owner. */
52+ #include <fcntl.h>
53+ #include <sys/stat.h>
54+ #include <unistd.h>
55+ #ifndef O_NOFOLLOW
56+ /* Older platforms lack O_NOFOLLOW; the lstat() pre-check still
57+ * rejects a symlinked leaf there. */
58+ #define O_NOFOLLOW 0
59+ #endif
60+ #endif
61+
4962#ifdef NO_INLINE
5063 #include <wolfssh/misc.h>
5164#else
@@ -237,20 +250,88 @@ static void freeBufferFromFile(byte* buf, void* heap)
237250}
238251
239252
240- /* set bufSz to size wanted if too small and buf is null */
241- static byte * getBufferFromFile (const char * fileName , word32 * bufSz , void * heap )
253+ /* Reads a file into a newly allocated buffer. When secure is non-zero the file
254+ * holds a trust anchor (host key, host cert, or user CA) so the load is refused
255+ * unless it is a regular file, reached without a symlink, owned by root or the
256+ * daemon's effective user, and not group or world writable. This blocks a local
257+ * user from substituting the file via a symlink or a writable parent directory.
258+ * set bufSz to size wanted if too small and buf is null */
259+ static byte * getBufferFromFile (const char * fileName , word32 * bufSz , void * heap ,
260+ int secure )
242261{
243262 FILE * file ;
244263 byte * buf = NULL ;
245264 long fileSz ;
246265 word32 readSz ;
266+ #ifndef _WIN32
267+ struct stat lst ;
268+ struct stat st ;
269+ int fd ;
270+ int flags ;
271+ #endif
247272
248273 WOLFSSH_UNUSED (heap );
274+ #ifdef _WIN32
275+ WOLFSSH_UNUSED (secure );
276+ #endif
249277
250278 if (fileName == NULL ) return NULL ;
251279
280+ #ifndef _WIN32
281+ if (secure ) {
282+ /* lstat() rejects a symlink or any non-regular leaf such as a FIFO or
283+ * device, O_NOFOLLOW keeps open() from following a symlink, and
284+ * O_NONBLOCK keeps open() from blocking on a FIFO swapped in after the
285+ * lstat(). The owner and mode checks run on the opened fd so there is
286+ * no window to swap the file after the check. */
287+ if (lstat (fileName , & lst ) != 0 || !S_ISREG (lst .st_mode )) {
288+ wolfSSH_Log (WS_LOG_ERROR ,
289+ "[SSHD] Refusing to load %s: missing or not a regular file" ,
290+ fileName );
291+ return NULL ;
292+ }
293+ fd = open (fileName , O_RDONLY | O_NOFOLLOW | O_NONBLOCK );
294+ if (fd < 0 ) {
295+ wolfSSH_Log (WS_LOG_ERROR , "[SSHD] Unable to open %s" , fileName );
296+ return NULL ;
297+ }
298+ if (fstat (fd , & st ) != 0 ) {
299+ wolfSSH_Log (WS_LOG_ERROR , "[SSHD] Unable to stat %s" , fileName );
300+ close (fd );
301+ return NULL ;
302+ }
303+ if (!S_ISREG (st .st_mode ) ||
304+ (st .st_uid != 0 && st .st_uid != geteuid ()) ||
305+ (st .st_mode & (S_IWGRP | S_IWOTH )) != 0 ) {
306+ wolfSSH_Log (WS_LOG_ERROR ,
307+ "[SSHD] Refusing to load %s: must be a regular file owned by "
308+ "root or the daemon and not group or world writable" , fileName );
309+ close (fd );
310+ return NULL ;
311+ }
312+ /* The target is a regular file, so clear O_NONBLOCK before the
313+ * buffered reads below, which expect ordinary blocking semantics. */
314+ flags = fcntl (fd , F_GETFL );
315+ if (flags != -1 )
316+ (void )fcntl (fd , F_SETFL , flags & ~O_NONBLOCK );
317+ file = fdopen (fd , "rb" );
318+ if (file == NULL ) {
319+ close (fd );
320+ return NULL ;
321+ }
322+ }
323+ else {
324+ if (WFOPEN (NULL , & file , fileName , "rb" ) != 0 )
325+ return NULL ;
326+ }
327+ #else
328+ /* The secure ownership and symlink gate is POSIX only. Windows has no
329+ * comparable uid model and relies on filesystem ACLs to protect the
330+ * trust-anchor files, so the file is opened directly regardless of the
331+ * secure flag. */
252332 if (WFOPEN (NULL , & file , fileName , "rb" ) != 0 )
253333 return NULL ;
334+ #endif
254335 WFSEEK (NULL , file , 0 , WSEEK_END );
255336 fileSz = WFTELL (NULL , file );
256337 if (fileSz < 0 ) {
@@ -331,7 +412,7 @@ static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
331412 if (ret == WS_SUCCESS ) {
332413#ifndef NO_FILESYSTEM
333414 * banner = getBufferFromFile (wolfSSHD_ConfigGetBanner (conf ),
334- NULL , heap );
415+ NULL , heap , 0 );
335416#endif
336417 if (* banner ) {
337418 wolfSSH_CTX_SetBanner (* ctx , (char * )* banner );
@@ -351,7 +432,7 @@ static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
351432 byte * data ;
352433 word32 dataSz = 0 ;
353434
354- data = getBufferFromFile (hostKey , & dataSz , heap );
435+ data = getBufferFromFile (hostKey , & dataSz , heap , 1 );
355436 if (data == NULL ) {
356437 wolfSSH_Log (WS_LOG_ERROR ,
357438 "[SSHD] Error reading host key file." );
@@ -395,7 +476,7 @@ static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
395476 byte * data ;
396477 word32 dataSz = 0 ;
397478
398- data = getBufferFromFile (hostCert , & dataSz , heap );
479+ data = getBufferFromFile (hostCert , & dataSz , heap , 1 );
399480 if (data == NULL ) {
400481 wolfSSH_Log (WS_LOG_ERROR ,
401482 "[SSHD] Error reading host key file." );
@@ -441,7 +522,7 @@ static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
441522
442523
443524 wolfSSH_Log (WS_LOG_INFO , "[SSHD] Using CA keys file %s" , caCert );
444- data = getBufferFromFile (caCert , & dataSz , heap );
525+ data = getBufferFromFile (caCert , & dataSz , heap , 1 );
445526 if (data == NULL ) {
446527 wolfSSH_Log (WS_LOG_ERROR ,
447528 "[SSHD] Error reading CA cert file." );
0 commit comments