6969
7070#ifndef _WIN32
7171#include <sys/types.h>
72+ #include <sys/stat.h>
7273#include <pwd.h>
7374#include <grp.h>
7475#include <errno.h>
@@ -526,8 +527,176 @@ static int ResolveAuthKeysPath(const char* homeDir, const char* pattern,
526527 return ret ;
527528}
528529
530+ /* OpenSSH "StrictModes" style permission/ownership check. Verifies that 'path'
531+ * is not group or world writable. When 'checkOwner' is set the file (and any
532+ * walked directory) must also be owned by 'uid' or by root. When 'checkChain'
533+ * is set, each parent directory up to and including 'homeDir' is checked the
534+ * same way (a writable ancestor directory would let an attacker substitute the
535+ * file). When 'noReadOthers' is set (used for secret files such as the host
536+ * private key) the file is also rejected if it is group or world readable.
537+ *
538+ * Public keys are not secret, so for authorized_keys we guard against write
539+ * access (key injection) and enforce ownership by the user. For the host
540+ * private key we guard against disclosure; ownership is not enforced there
541+ * because the server may run privileged (e.g. via sudo) against a key owned by
542+ * an unprivileged service account.
543+ *
544+ * Returns WS_SUCCESS when the path is considered safe. On platforms without
545+ * POSIX ownership/mode semantics this is a no-op returning WS_SUCCESS. */
546+ int wolfSSHD_CheckFilePermissions (const char * path , const char * homeDir ,
547+ WUID_T uid , int checkOwner , int checkChain , int noReadOthers )
548+ {
549+ #ifndef _WIN32
550+ int ret = WS_SUCCESS ;
551+ int fileInHome ;
552+ struct stat s ;
553+ char pathBuf [MAX_PATH_SZ ];
554+ char * slash ;
555+ word32 pathSz ;
556+ word32 homeLen ;
557+ word32 i ;
558+
559+ if (path == NULL ) {
560+ ret = WS_BAD_ARGUMENT ;
561+ }
562+
563+ /* check the target file itself. stat() (not lstat()) is used so symlinked
564+ * key paths resolve to their target and are validated, rather than being
565+ * rejected outright. */
566+ if (ret == WS_SUCCESS ) {
567+ if (stat (path , & s ) != 0 ) {
568+ wolfSSH_Log (WS_LOG_ERROR ,
569+ "[SSHD] Unable to stat %s for permission check" , path );
570+ ret = WS_BAD_FILE_E ;
571+ }
572+ else if (!S_ISREG (s .st_mode )) {
573+ wolfSSH_Log (WS_LOG_ERROR ,
574+ "[SSHD] %s is not a regular file" , path );
575+ ret = WS_BAD_FILE_E ;
576+ }
577+ }
578+ if (ret == WS_SUCCESS && checkOwner ) {
579+ if (s .st_uid != uid && s .st_uid != 0 ) {
580+ wolfSSH_Log (WS_LOG_ERROR ,
581+ "[SSHD] Bad owner on %s, must be owned by the user or root" ,
582+ path );
583+ ret = WS_BAD_FILE_E ;
584+ }
585+ }
586+ if (ret == WS_SUCCESS ) {
587+ if ((s .st_mode & (S_IWGRP | S_IWOTH )) != 0 ) {
588+ wolfSSH_Log (WS_LOG_ERROR ,
589+ "[SSHD] %s is group or world writable" , path );
590+ ret = WS_BAD_FILE_E ;
591+ }
592+ }
593+ if (ret == WS_SUCCESS && noReadOthers ) {
594+ if ((s .st_mode & (S_IRGRP | S_IROTH )) != 0 ) {
595+ wolfSSH_Log (WS_LOG_ERROR ,
596+ "[SSHD] %s is group or world readable" , path );
597+ ret = WS_BAD_FILE_E ;
598+ }
599+ }
600+
601+ /* Walk parent directories checking that none is group or world writable,
602+ * since a writable ancestor would let another user rename it and substitute
603+ * the key file. When the file is inside the user's home directory the walk
604+ * stops at the home directory and also enforces ownership (the user/root
605+ * must own the file and each directory down from home). When the file is a
606+ * custom AuthorizedKeysFile outside the home directory, the walk continues
607+ * to the filesystem root but only checks writability, not ownership, since
608+ * those ancestors are system-managed and not owned by the user.
609+ *
610+ * homeLen is normalized to ignore any trailing slash on homeDir so the
611+ * subtree test does not depend on how pw_dir is formatted. */
612+ homeLen = (homeDir != NULL ) ? (word32 )WSTRLEN (homeDir ) : 0 ;
613+ while (homeLen > 0 && homeDir [homeLen - 1 ] == '/' ) {
614+ homeLen -- ;
615+ }
616+ fileInHome = (homeDir != NULL &&
617+ WSTRNCMP (path , homeDir , homeLen ) == 0 &&
618+ (path [homeLen ] == '/' || path [homeLen ] == '\0' ));
619+ if (ret == WS_SUCCESS && checkChain && homeDir != NULL ) {
620+ pathSz = (word32 )WSTRLEN (path );
621+ if (pathSz >= MAX_PATH_SZ ) {
622+ ret = WS_BAD_FILE_E ;
623+ }
624+ else {
625+ WMEMCPY (pathBuf , path , pathSz );
626+ pathBuf [pathSz ] = '\0' ;
627+
628+ while (ret == WS_SUCCESS ) {
629+ /* trim the last path component to move up one directory */
630+ slash = NULL ;
631+ for (i = 0 ; pathBuf [i ] != '\0' ; i ++ ) {
632+ if (pathBuf [i ] == '/' ) {
633+ slash = & pathBuf [i ];
634+ }
635+ }
636+ if (slash == NULL ) {
637+ break ; /* no more parent directories */
638+ }
639+ if (slash == pathBuf ) {
640+ pathBuf [1 ] = '\0' ; /* parent is root "/" */
641+ }
642+ else {
643+ * slash = '\0' ;
644+ }
645+
646+ if (stat (pathBuf , & s ) != 0 ) {
647+ wolfSSH_Log (WS_LOG_ERROR ,
648+ "[SSHD] Unable to stat directory %s" , pathBuf );
649+ ret = WS_BAD_FILE_E ;
650+ break ;
651+ }
652+ if (!S_ISDIR (s .st_mode )) {
653+ wolfSSH_Log (WS_LOG_ERROR ,
654+ "[SSHD] %s is not a directory" , pathBuf );
655+ ret = WS_BAD_FILE_E ;
656+ break ;
657+ }
658+ if (checkOwner && fileInHome &&
659+ s .st_uid != uid && s .st_uid != 0 ) {
660+ wolfSSH_Log (WS_LOG_ERROR ,
661+ "[SSHD] Bad owner on directory %s" , pathBuf );
662+ ret = WS_BAD_FILE_E ;
663+ break ;
664+ }
665+ if ((s .st_mode & (S_IWGRP | S_IWOTH )) != 0 ) {
666+ wolfSSH_Log (WS_LOG_ERROR ,
667+ "[SSHD] Directory %s is group or world writable" ,
668+ pathBuf );
669+ ret = WS_BAD_FILE_E ;
670+ break ;
671+ }
672+
673+ /* stop at the home directory (in-home files) or the
674+ * filesystem root (out-of-home files) */
675+ if ((fileInHome &&
676+ WSTRNCMP (pathBuf , homeDir , homeLen ) == 0 &&
677+ pathBuf [homeLen ] == '\0' ) ||
678+ WSTRCMP (pathBuf , "/" ) == 0 ) {
679+ break ;
680+ }
681+ }
682+ }
683+ }
684+
685+ return ret ;
686+ #else
687+ WOLFSSH_UNUSED (path );
688+ WOLFSSH_UNUSED (homeDir );
689+ WOLFSSH_UNUSED (uid );
690+ WOLFSSH_UNUSED (checkOwner );
691+ WOLFSSH_UNUSED (checkChain );
692+ WOLFSSH_UNUSED (noReadOthers );
693+ return WS_SUCCESS ;
694+ #endif
695+ }
696+
529697static int SearchForPubKey (const char * path , const char * authKeysFile ,
530- const WS_UserAuthData_PublicKey * pubKeyCtx )
698+ const WS_UserAuthData_PublicKey * pubKeyCtx ,
699+ WUID_T uid , int strictModes )
531700{
532701 int ret = WSSHD_AUTH_SUCCESS ;
533702 char authKeysPath [MAX_PATH_SZ ];
@@ -546,6 +715,20 @@ static int SearchForPubKey(const char* path, const char* authKeysFile,
546715 ret = rc ;
547716 }
548717
718+ /* When StrictModes is enabled, refuse the authorized keys file if it or any
719+ * parent directory up to the home directory is owned by another user or is
720+ * group/world writable. */
721+ if (ret == WSSHD_AUTH_SUCCESS && strictModes ) {
722+ if (wolfSSHD_CheckFilePermissions (authKeysPath , path , uid ,
723+ 1 /* checkOwner */ , 1 /* checkChain */ , 0 /* noReadOthers */ )
724+ != WS_SUCCESS ) {
725+ wolfSSH_Log (WS_LOG_ERROR ,
726+ "[SSHD] Authorized keys file %s failed StrictModes check" ,
727+ authKeysPath );
728+ ret = WSSHD_AUTH_FAILURE ;
729+ }
730+ }
731+
549732 if (ret == WSSHD_AUTH_SUCCESS ) {
550733 if (WFOPEN (NULL , & f , authKeysPath , "rb" ) != 0 ) {
551734 wolfSSH_Log (WS_LOG_ERROR , "[SSHD] Unable to open %s" ,
@@ -705,7 +888,8 @@ static int CheckPublicKeyUnix(const char* name,
705888 }
706889
707890 if (ret == WSSHD_AUTH_SUCCESS ) {
708- ret = SearchForPubKey (pwInfo -> pw_dir , authorizedKeysFile , pubKeyCtx );
891+ ret = SearchForPubKey (pwInfo -> pw_dir , authorizedKeysFile , pubKeyCtx ,
892+ pwInfo -> pw_uid , wolfSSHD_ConfigGetStrictModes (authCtx -> conf ));
709893 }
710894 }
711895
@@ -1049,7 +1233,8 @@ static int CheckPublicKeyWIN(const char* usr,
10491233 if (ret == WSSHD_AUTH_SUCCESS ) {
10501234 r [rSz - 1 ] = L'\0' ;
10511235
1052- ret = SearchForPubKey (r , authorizedKeysFile , pubKeyCtx );
1236+ ret = SearchForPubKey (r , authorizedKeysFile , pubKeyCtx , 0 ,
1237+ wolfSSHD_ConfigGetStrictModes (authCtx -> conf ));
10531238 if (ret != WSSHD_AUTH_SUCCESS ) {
10541239 wolfSSH_Log (WS_LOG_ERROR ,
10551240 "[SSHD] Failed to find public key for user %s" , usr );
0 commit comments