From 105d65bfedfa0e381dcebd197ef67aab799fc8b1 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 29 Feb 2016 22:33:23 -0500 Subject: Check filesystem permissions on NSS database at startup See if the configured user has read access to the NSS database during initialization so the server can gracefully shutdown rather than ending up in a forking loop because the database is owned by root and is therefore unreadable once Apache starts forking. Adds a new configuration option, NSSSkipPermissionCheck , to skip this check in case something goes wrong. https://fedorahosted.org/mod_nss/ticket/3 --- docs/mod_nss.html | 12 ++++++ mod_nss.c | 3 ++ mod_nss.h | 3 ++ nss_engine_config.c | 11 +++++ nss_engine_init.c | 122 ++++++++++++++++++++++++++++++++++++++++------------ 5 files changed, 124 insertions(+), 27 deletions(-) diff --git a/docs/mod_nss.html b/docs/mod_nss.html index c84f938..ec03a07 100644 --- a/docs/mod_nss.html +++ b/docs/mod_nss.html @@ -440,6 +440,18 @@ reads that many bytes, otherwise it reads until the program exits.
NSSRandomSeed startup /dev/urandom 512
NSSRandomSeed startup /usr/bin/makerandom

+NSSSkipPermissionCheck
+
+The NSS database will be checked to ensure that the user configured +to run Apache as has owner or group read access to the database +configured in NSSCertificateDatabase. This check +can be disabled by setting NSSSkipPermissionCheck +to on. The default is off
+
+Example
+
+NSSSkipPermissionCheck on
+
NSSEngine

Enables or disables the SSL protocol. This is usually used within a diff --git a/mod_nss.c b/mod_nss.c index b3ea6f0..38098c8 100644 --- a/mod_nss.c +++ b/mod_nss.c @@ -54,6 +54,9 @@ static const command_rec nss_config_cmds[] = { SSL_CMD_SRV(SessionCacheSize, TAKE1, "SSL Session Cache size " "(`N' - number of entries)") + SSL_CMD_SRV(SkipPermissionCheck, FLAG, + "Skip checking the NSS database read permissions" + "(`on', `off')") SSL_CMD_SRV(PassPhraseDialog, TAKE1, "SSL dialog mechanism for the pass phrase query " "(`builtin', `file:/path/to/file`") diff --git a/mod_nss.h b/mod_nss.h index ac56ddb..4604ff9 100644 --- a/mod_nss.h +++ b/mod_nss.h @@ -256,6 +256,8 @@ typedef struct { const char *pphrase_dialog_path; const char *pphrase_dialog_helper; + BOOL skip_permission_check; + apr_proc_t proc; apr_procattr_t *procattr; @@ -418,6 +420,7 @@ const char *nss_cmd_NSSSessionCacheSize(cmd_parms *cmd, void *dcfg, const char * const char *nss_cmd_NSSPassPhraseDialog(cmd_parms *cmd, void *dcfg, const char *arg); const char *nss_cmd_NSSPassPhraseHelper(cmd_parms *cmd, void *dcfg, const char *arg); const char *nss_cmd_NSSRandomSeed(cmd_parms *, void *, const char *, const char *, const char *); +const char *nss_cmd_NSSSkipPermissionCheck(cmd_parms *cmd, void *dcfg, int flag); const char *nss_cmd_NSSSessionTickets(cmd_parms *cmd, void *dcfg, int flag); #ifdef ENABLE_SERVER_DHE const char *nss_cmd_NSSServerDHE(cmd_parms *cmd, void *dcfg, int flag); diff --git a/nss_engine_config.c b/nss_engine_config.c index c0c7155..4adff52 100644 --- a/nss_engine_config.c +++ b/nss_engine_config.c @@ -54,6 +54,7 @@ SSLModConfigRec *nss_config_global_create(server_rec *s) mc->aRandSeed = apr_array_make(pool, 4, sizeof(ssl_randseed_t)); mc->semid = 0; + mc->skip_permission_check = PR_FALSE; apr_pool_userdata_set(mc, SSL_MOD_CONFIG_KEY, apr_pool_cleanup_null, @@ -803,6 +804,16 @@ const char *nss_cmd_NSSRandomSeed(cmd_parms *cmd, return NULL; } +const char *nss_cmd_NSSSkipPermissionCheck(cmd_parms *cmd, + void *dcfg, int flag) +{ + SSLModConfigRec *mc = myModConfig(cmd->server); + + mc->skip_permission_check = flag ? PR_TRUE: PR_FALSE; + + return NULL; +} + const char *nss_cmd_NSSSessionTickets(cmd_parms *cmd, void *dcfg, int flag) { diff --git a/nss_engine_init.c b/nss_engine_init.c index 81b2434..2a6ce58 100644 --- a/nss_engine_init.c +++ b/nss_engine_init.c @@ -27,6 +27,8 @@ #include "ocsp.h" #include "keyhi.h" #include "cert.h" +#include +#include static SECStatus ownBadCertHandler(void *arg, PRFileDesc * socket); static SECStatus ownHandshakeCallback(PRFileDesc * socket, void *arg); @@ -49,6 +51,32 @@ static char *version_components[] = { NULL }; +/* See if a uid or gid can read a file at a given path. Ignore world + * read permissions. + * + * Return 0 on failure or file doesn't exist + * Return 1 on success + */ +static int check_path(uid_t uid, gid_t gid, char *filepath, apr_pool_t *p) +{ + apr_finfo_t finfo; + int rv; + + if ((rv = apr_stat(&finfo, filepath, APR_FINFO_PROT | APR_FINFO_OWNER, + p)) == APR_SUCCESS) { + if (((uid == finfo.user) && + ((finfo.protection & APR_FPROT_UREAD))) || + ((gid == finfo.group) && + ((finfo.protection & APR_FPROT_GREAD))) + ) + { + return 1; + } + return 0; + } + return 0; +} + static char *nss_add_version_component(apr_pool_t *p, server_rec *s, char *name) @@ -130,6 +158,68 @@ static void nss_init_SSLLibrary(server_rec *base_server, apr_pool_t *p) } } + /* Assuming everything is ok so far, check the cert database permissions + * for the server user before Apache starts forking. We die now or + * get stuck in an endless loop not able to read the NSS database. + */ + if (mc->nInitCount == 1) { + if (mc->skip_permission_check == PR_FALSE) { + char filepath[1024]; + struct passwd *pw = NULL; + + pw = getpwnam(mc->user); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, + "Checking permissions for user %s: uid %d gid %d", + mc->user, pw->pw_uid, pw->pw_gid); + + if (strncasecmp(mc->pCertificateDatabase, "sql:", 4) == 0) { + apr_snprintf(filepath, 1024, "%s/key4.db", + mc->pCertificateDatabase+4); + if (!(check_path(pw->pw_uid, pw->pw_gid, filepath, p))) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, + "Server user %s lacks read access to NSS key " + "database %s.", mc->user, filepath); + nss_die(); + } + apr_snprintf(filepath, 1024, "%s/cert9.db", + mc->pCertificateDatabase+4); + if (!(check_path(pw->pw_uid, pw->pw_gid, filepath, p))) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, + "Server user %s lacks read access to NSS cert " + "database %s.", mc->user, filepath); + nss_die(); + } + } else { + apr_snprintf(filepath, 1024, "%s/key3.db", + mc->pCertificateDatabase); + if (!(check_path(pw->pw_uid, pw->pw_gid, filepath, p))) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, + "Server user %s lacks read access to NSS key " + "database %s.", mc->user, filepath); + nss_die(); + } + apr_snprintf(filepath, 1024, "%s/cert8.db", + mc->pCertificateDatabase); + if (!(check_path(pw->pw_uid, pw->pw_gid, filepath, p))) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, + "Server user %s lacks read access to NSS cert " + "database %s.", mc->user, filepath); + nss_die(); + } + apr_snprintf(filepath, 1024, "%s/secmod.db", + mc->pCertificateDatabase); + if (!(check_path(pw->pw_uid, pw->pw_gid, filepath, p))) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, + "Server user %s lacks read access to NSS secmod " + "database %s.", mc->user, filepath); + nss_die(); + } + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, + "Permissions check out ok"); + } + } + /* We need to be in the same directory as libnssckbi.so to load the * root certificates properly. */ @@ -155,6 +245,7 @@ static void nss_init_SSLLibrary(server_rec *base_server, apr_pool_t *p) else return; } + /* Initialize NSS and open the certificate database read-only. */ rv = NSS_Initialize(mc->pCertificateDatabase, mc->pDBPrefix, mc->pDBPrefix, "secmod.db", NSS_INIT_READONLY); if (chdir(cwd) != 0) { @@ -168,39 +259,16 @@ static void nss_init_SSLLibrary(server_rec *base_server, apr_pool_t *p) return; } - /* Assuming everything is ok so far, check the cert database password(s). */ if (rv != SECSuccess) { - apr_finfo_t finfo; - char keypath[1024]; - int rv; - uid_t user_id; - gid_t gid; - - user_id = ap_uname2id(mc->user); - gid = getegid(); - NSS_Shutdown(); + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, "NSS_Initialize failed. Certificate database: %s.", mc->pCertificateDatabase != NULL ? mc->pCertificateDatabase : "not set in configuration"); nss_log_nss_error(APLOG_MARK, APLOG_ERR, base_server); - apr_snprintf(keypath, 1024, "%s/key3.db", mc->pCertificateDatabase); - if ((rv = apr_stat(&finfo, keypath, APR_FINFO_PROT | APR_FINFO_OWNER, - p)) == APR_SUCCESS) { - if (((user_id == finfo.user) && - (!(finfo.protection & APR_FPROT_UREAD))) || - ((gid == finfo.group) && - (!(finfo.protection & APR_FPROT_GREAD))) || - (!(finfo.protection & APR_FPROT_WREAD)) - ) - { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, - "Server user lacks read access to NSS database."); - } - } else { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, - "Does the NSS database exist?"); - } + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, + "Does the NSS database exist?"); + nss_die(); } -- cgit