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,153 @@ 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+ struct stat s ;
552+ char pathBuf [MAX_PATH_SZ ];
553+ char * slash ;
554+ word32 pathSz ;
555+ word32 i ;
556+
557+ if (path == NULL ) {
558+ ret = WS_BAD_ARGUMENT ;
559+ }
560+
561+ /* check the target file itself. stat() (not lstat()) is used so symlinked
562+ * key paths resolve to their target and are validated like OpenSSH, rather
563+ * than being rejected outright. */
564+ if (ret == WS_SUCCESS ) {
565+ if (stat (path , & s ) != 0 ) {
566+ wolfSSH_Log (WS_LOG_ERROR ,
567+ "[SSHD] Unable to stat %s for permission check" , path );
568+ ret = WS_BAD_FILE_E ;
569+ }
570+ else if (!S_ISREG (s .st_mode )) {
571+ wolfSSH_Log (WS_LOG_ERROR ,
572+ "[SSHD] %s is not a regular file" , path );
573+ ret = WS_BAD_FILE_E ;
574+ }
575+ }
576+ if (ret == WS_SUCCESS && checkOwner ) {
577+ if (s .st_uid != uid && s .st_uid != 0 ) {
578+ wolfSSH_Log (WS_LOG_ERROR ,
579+ "[SSHD] Bad owner on %s, must be owned by the user or root" ,
580+ path );
581+ ret = WS_BAD_FILE_E ;
582+ }
583+ }
584+ if (ret == WS_SUCCESS ) {
585+ if ((s .st_mode & (S_IWGRP | S_IWOTH )) != 0 ) {
586+ wolfSSH_Log (WS_LOG_ERROR ,
587+ "[SSHD] %s is group or world writable" , path );
588+ ret = WS_BAD_FILE_E ;
589+ }
590+ }
591+ if (ret == WS_SUCCESS && noReadOthers ) {
592+ if ((s .st_mode & (S_IRGRP | S_IROTH )) != 0 ) {
593+ wolfSSH_Log (WS_LOG_ERROR ,
594+ "[SSHD] %s is group or world readable" , path );
595+ ret = WS_BAD_FILE_E ;
596+ }
597+ }
598+
599+ /* walk parent directories up to and including homeDir */
600+ if (ret == WS_SUCCESS && checkChain && homeDir != NULL ) {
601+ pathSz = (word32 )WSTRLEN (path );
602+ if (pathSz >= MAX_PATH_SZ ) {
603+ ret = WS_BAD_FILE_E ;
604+ }
605+ else {
606+ WMEMCPY (pathBuf , path , pathSz );
607+ pathBuf [pathSz ] = '\0' ;
608+
609+ while (ret == WS_SUCCESS ) {
610+ /* trim the last path component to move up one directory */
611+ slash = NULL ;
612+ for (i = 0 ; pathBuf [i ] != '\0' ; i ++ ) {
613+ if (pathBuf [i ] == '/' ) {
614+ slash = & pathBuf [i ];
615+ }
616+ }
617+ if (slash == NULL ) {
618+ break ; /* no more parent directories */
619+ }
620+ if (slash == pathBuf ) {
621+ pathBuf [1 ] = '\0' ; /* parent is root "/" */
622+ }
623+ else {
624+ * slash = '\0' ;
625+ }
626+
627+ if (stat (pathBuf , & s ) != 0 ) {
628+ wolfSSH_Log (WS_LOG_ERROR ,
629+ "[SSHD] Unable to stat directory %s" , pathBuf );
630+ ret = WS_BAD_FILE_E ;
631+ break ;
632+ }
633+ if (!S_ISDIR (s .st_mode )) {
634+ wolfSSH_Log (WS_LOG_ERROR ,
635+ "[SSHD] %s is not a directory" , pathBuf );
636+ ret = WS_BAD_FILE_E ;
637+ break ;
638+ }
639+ if (checkOwner && s .st_uid != uid && s .st_uid != 0 ) {
640+ wolfSSH_Log (WS_LOG_ERROR ,
641+ "[SSHD] Bad owner on directory %s" , pathBuf );
642+ ret = WS_BAD_FILE_E ;
643+ break ;
644+ }
645+ if ((s .st_mode & (S_IWGRP | S_IWOTH )) != 0 ) {
646+ wolfSSH_Log (WS_LOG_ERROR ,
647+ "[SSHD] Directory %s is group or world writable" ,
648+ pathBuf );
649+ ret = WS_BAD_FILE_E ;
650+ break ;
651+ }
652+
653+ /* stop after checking the home directory or filesystem root */
654+ if (WSTRCMP (pathBuf , homeDir ) == 0 ||
655+ WSTRCMP (pathBuf , "/" ) == 0 ) {
656+ break ;
657+ }
658+ }
659+ }
660+ }
661+
662+ return ret ;
663+ #else
664+ WOLFSSH_UNUSED (path );
665+ WOLFSSH_UNUSED (homeDir );
666+ WOLFSSH_UNUSED (uid );
667+ WOLFSSH_UNUSED (checkOwner );
668+ WOLFSSH_UNUSED (checkChain );
669+ WOLFSSH_UNUSED (noReadOthers );
670+ return WS_SUCCESS ;
671+ #endif
672+ }
673+
529674static int SearchForPubKey (const char * path , const char * authKeysFile ,
530- const WS_UserAuthData_PublicKey * pubKeyCtx )
675+ const WS_UserAuthData_PublicKey * pubKeyCtx ,
676+ WUID_T uid , int strictModes )
531677{
532678 int ret = WSSHD_AUTH_SUCCESS ;
533679 char authKeysPath [MAX_PATH_SZ ];
@@ -546,6 +692,19 @@ static int SearchForPubKey(const char* path, const char* authKeysFile,
546692 ret = rc ;
547693 }
548694
695+ /* When StrictModes is enabled, refuse the authorized keys file if it or any
696+ * parent directory up to the home directory is owned by another user or is
697+ * group/world writable, mirroring OpenSSH. */
698+ if (ret == WSSHD_AUTH_SUCCESS && strictModes ) {
699+ if (wolfSSHD_CheckFilePermissions (authKeysPath , path , uid , 1 , 1 , 0 )
700+ != WS_SUCCESS ) {
701+ wolfSSH_Log (WS_LOG_ERROR ,
702+ "[SSHD] Authorized keys file %s failed StrictModes check" ,
703+ authKeysPath );
704+ ret = WSSHD_AUTH_FAILURE ;
705+ }
706+ }
707+
549708 if (ret == WSSHD_AUTH_SUCCESS ) {
550709 if (WFOPEN (NULL , & f , authKeysPath , "rb" ) != 0 ) {
551710 wolfSSH_Log (WS_LOG_ERROR , "[SSHD] Unable to open %s" ,
@@ -705,7 +864,8 @@ static int CheckPublicKeyUnix(const char* name,
705864 }
706865
707866 if (ret == WSSHD_AUTH_SUCCESS ) {
708- ret = SearchForPubKey (pwInfo -> pw_dir , authorizedKeysFile , pubKeyCtx );
867+ ret = SearchForPubKey (pwInfo -> pw_dir , authorizedKeysFile , pubKeyCtx ,
868+ pwInfo -> pw_uid , wolfSSHD_ConfigGetStrictModes (authCtx -> conf ));
709869 }
710870 }
711871
@@ -1049,7 +1209,8 @@ static int CheckPublicKeyWIN(const char* usr,
10491209 if (ret == WSSHD_AUTH_SUCCESS ) {
10501210 r [rSz - 1 ] = L'\0' ;
10511211
1052- ret = SearchForPubKey (r , authorizedKeysFile , pubKeyCtx );
1212+ ret = SearchForPubKey (r , authorizedKeysFile , pubKeyCtx , 0 ,
1213+ wolfSSHD_ConfigGetStrictModes (authCtx -> conf ));
10531214 if (ret != WSSHD_AUTH_SUCCESS ) {
10541215 wolfSSH_Log (WS_LOG_ERROR ,
10551216 "[SSHD] Failed to find public key for user %s" , usr );
0 commit comments