diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cabf045..49006e29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,3 +45,4 @@ jobs: run: make -j2 - name: make install run: make install + diff --git a/include/cache.h b/include/cache.h index 644a0b43..a9d53a98 100644 --- a/include/cache.h +++ b/include/cache.h @@ -44,7 +44,7 @@ void load_help(void); void send_user_motd(struct Client *); void send_user_rules(struct Client *); void send_oper_motd(struct Client *); -void cache_user_motd(void); +void cache_user_motd(struct Client *source_p); void cache_user_rules(void); struct Dictionary; diff --git a/include/send.h b/include/send.h index 290f13a5..f30799e4 100644 --- a/include/send.h +++ b/include/send.h @@ -64,6 +64,7 @@ extern void sendto_channel_local(int type, struct Channel *, const char *, ...) extern void sendto_channel_local_butone(struct Client *, int type, struct Channel *, const char *, ...) AFP(4, 5); extern void sendto_channel_local_with_capability(int type, int caps, int negcaps, struct Channel *, const char *, ...) AFP(5, 6); +extern void sendto_channel_local_with_capability_butone(struct Client *, int type, int caps, int negcaps, struct Channel *, const char *, ...) AFP(6, 7); extern void sendto_common_channels_local(struct Client *, int cap, const char *, ...) AFP(3, 4); extern void sendto_common_channels_local_butone(struct Client *, int cap, const char *, ...) AFP(3, 4); diff --git a/libratbox/src/openssl.c b/libratbox/src/openssl.c index 0c6b657b..c1cf9edb 100644 --- a/libratbox/src/openssl.c +++ b/libratbox/src/openssl.c @@ -392,25 +392,69 @@ rb_setup_ssl_server(const char *const certfile, const char *keyfile, } else { - FILE *const dhf = fopen(dhfile, "r"); + BIO *const bio = BIO_new_file(dhfile, "r"); DH *dhp = NULL; - if(dhf == NULL) + if(bio == NULL) { - rb_lib_log("%s: fopen ('%s'): %s", __func__, dhfile, strerror(errno)); + rb_lib_log("%s: BIO_new_file ('%s'): %s", __func__, dhfile, strerror(errno)); } - else if(PEM_read_DHparams(dhf, &dhp, NULL, NULL) == NULL) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + /* OpenSSL 3.0+ - use EVP_PKEY API */ + else + { + EVP_PKEY *pkey = NULL; + OSSL_DECODER_CTX *ctx = NULL; + + ctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "PEM", NULL, "DH", + OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + NULL, NULL); + if(ctx == NULL) + { + rb_lib_log("%s: OSSL_DECODER_CTX_new_for_pkey: %s", __func__, + rb_ssl_strerror(rb_ssl_last_err())); + BIO_free(bio); + } + else if(OSSL_DECODER_from_bio(ctx, bio) == 1) + { + dhp = EVP_PKEY_get1_DH(pkey); + if(dhp != NULL) + { + SSL_CTX_set_tmp_dh(ssl_ctx_new, dhp); + DH_free(dhp); + EVP_PKEY_free(pkey); + } + else + { + rb_lib_log("%s: EVP_PKEY_get1_DH: %s", __func__, + rb_ssl_strerror(rb_ssl_last_err())); + EVP_PKEY_free(pkey); + } + } + else + { + rb_lib_log("%s: OSSL_DECODER_from_bio ('%s'): %s", __func__, dhfile, + rb_ssl_strerror(rb_ssl_last_err())); + EVP_PKEY_free(pkey); + } + OSSL_DECODER_CTX_free(ctx); + BIO_free(bio); + } +#else + /* OpenSSL < 3.0 or LibreSSL - use legacy API */ + else if(PEM_read_bio_DHparams(bio, &dhp, NULL, NULL) == NULL) { - rb_lib_log("%s: PEM_read_DHparams ('%s'): %s", __func__, dhfile, + rb_lib_log("%s: PEM_read_bio_DHparams ('%s'): %s", __func__, dhfile, rb_ssl_strerror(rb_ssl_last_err())); - fclose(dhf); + BIO_free(bio); } else { SSL_CTX_set_tmp_dh(ssl_ctx_new, dhp); DH_free(dhp); - fclose(dhf); + BIO_free(bio); } +#endif } if(SSL_CTX_set_cipher_list(ssl_ctx_new, cipherlist) != 1) diff --git a/libratbox/src/openssl_ratbox.h b/libratbox/src/openssl_ratbox.h index 0e2b40ff..714c246e 100644 --- a/libratbox/src/openssl_ratbox.h +++ b/libratbox/src/openssl_ratbox.h @@ -30,8 +30,12 @@ #include #include #include +#include #include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) +#include +#endif /* * A long time ago, in a world far away, OpenSSL had a well-established mechanism for ensuring compatibility with diff --git a/modules/m_rehash.c b/modules/m_rehash.c index d8730d7e..589db509 100644 --- a/modules/m_rehash.c +++ b/modules/m_rehash.c @@ -86,13 +86,11 @@ rehash_dns(struct Client *source_p) static void rehash_ssld(struct Client *source_p) { - if (!IsOperAdmin(source_p)) { - sendto_one(source_p, form_str(ERR_NOPRIVS), - me.name, source_p->name, "admin"); - return; - } - sendto_realops_snomask(SNO_GENERAL, L_ALL, "%s is restarting ssld", + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, + "%s is restarting ssld", get_oper_name(source_p)); + if (!MyConnect(source_p)) + remote_rehash_oper_p = source_p; restart_ssld(); } @@ -100,42 +98,61 @@ rehash_ssld(struct Client *source_p) static void rehash_motd(struct Client *source_p) { - struct stat sb; - struct tm *local_tm; - sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "%s is forcing re-reading of MOTD file", get_oper_name(source_p)); if (!MyConnect(source_p)) remote_rehash_oper_p = source_p; - free_cachefile(user_motd); - user_motd = cache_file(MPATH, "ircd.motd", 0); + cache_user_motd(source_p); +} + +static void +rehash_rules(struct Client *source_p) +{ + struct cachefile *old_rules; + struct cachefile *new_rules; - if(stat(MPATH, &sb) == 0) { - local_tm = localtime(&sb.st_mtime); - - if(local_tm != NULL) { - rb_snprintf(user_motd_changed, sizeof(user_motd_changed), - "%d/%d/%d %d:%d", - local_tm->tm_mday, local_tm->tm_mon + 1, - 1900 + local_tm->tm_year, local_tm->tm_hour, - local_tm->tm_min); - } + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, + "%s is forcing re-reading of RULES file", + get_oper_name(source_p)); + if (!MyConnect(source_p)) + remote_rehash_oper_p = source_p; + + /* Load new cache first, then swap pointer, then free old cache. + * This prevents use-after-free if send_user_rules is iterating. */ + new_rules = cache_file(RPATH, "ircd.rules", 0); + if(new_rules != NULL) { + /* Only update if new cache loaded successfully */ + old_rules = user_rules; + user_rules = new_rules; + free_cachefile(old_rules); } + /* If new_rules is NULL (file missing/error), keep old RULES */ } static void rehash_omotd(struct Client *source_p) { + struct cachefile *old_motd; + struct cachefile *new_motd; + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "%s is forcing re-reading of OPER MOTD file", get_oper_name(source_p)); if (!MyConnect(source_p)) remote_rehash_oper_p = source_p; - free_cachefile(oper_motd); - oper_motd = cache_file(OPATH, "opers.motd", 0); + /* Load new cache first, then swap pointer, then free old cache. + * This prevents use-after-free if send_oper_motd is iterating. */ + new_motd = cache_file(OPATH, "opers.motd", 0); + if(new_motd != NULL) { + /* Only update if new cache loaded successfully */ + old_motd = oper_motd; + oper_motd = new_motd; + free_cachefile(old_motd); + } + /* If new_motd is NULL (file missing/error), keep old MOTD */ } static void diff --git a/src/cache.c b/src/cache.c index 5ab83aff..6b7051c5 100644 --- a/src/cache.c +++ b/src/cache.c @@ -117,8 +117,11 @@ cache_file(const char *filename, const char *shortname, int flags) if((in = fopen(filename, "r")) == NULL) return NULL; - cacheptr = rb_malloc(sizeof(struct cachefile)); + if(cacheptr == NULL) { + fclose(in); + return NULL; + } rb_strlcpy(cacheptr->name, shortname, sizeof(cacheptr->name)); cacheptr->flags = flags; @@ -130,10 +133,26 @@ cache_file(const char *filename, const char *shortname, int flags) if(!EmptyString(line)) { lineptr = rb_malloc(sizeof(struct cacheline)); + if(lineptr == NULL) { + /* Memory allocation failed - free what we have and abort */ + free_cachefile(cacheptr); + fclose(in); + return NULL; + } untabify(lineptr->data, line, sizeof(lineptr->data)); rb_dlinkAddTail(lineptr, &lineptr->linenode, &cacheptr->contents); - } else - rb_dlinkAddTailAlloc(emptyline, &cacheptr->contents); + } else { + /* Only add emptyline if it's initialized */ + if(emptyline != NULL) + rb_dlinkAddTailAlloc(emptyline, &cacheptr->contents); + } + } + + /* Check for read errors */ + if(ferror(in)) { + free_cachefile(cacheptr); + fclose(in); + return NULL; } if (0 == rb_dlink_list_length(&cacheptr->contents)) { @@ -165,6 +184,10 @@ cache_links(void *unused) RB_DLINK_FOREACH(ptr, global_serv_list.head) { target_p = ptr->data; + /* Skip NULL pointers to prevent crashes */ + if(target_p == NULL) + continue; + /* skip ourselves (done in /links) and hidden servers */ if(IsMe(target_p) || (IsHidden(target_p) && !ConfigServerHide.disable_hidden)) @@ -199,7 +222,6 @@ free_cachefile(struct cachefile *cacheptr) RB_DLINK_FOREACH_SAFE(ptr, next_ptr, cacheptr->contents.head) { if(ptr->data != emptyline) { struct cacheline *line = ptr->data; - rb_free(line->data); rb_free(line); } else { rb_free_rb_dlink_node(ptr); @@ -248,7 +270,8 @@ load_help(void) continue; rb_snprintf(filename, sizeof(filename), "%s/%s", HPATH, ldirent->d_name); cacheptr = cache_file(filename, ldirent->d_name, HELP_OPER); - irc_dictionary_add(help_dict_oper, cacheptr->name, cacheptr); + if(cacheptr != NULL) + irc_dictionary_add(help_dict_oper, cacheptr->name, cacheptr); } closedir(helpfile_dir); @@ -280,7 +303,8 @@ load_help(void) #endif cacheptr = cache_file(filename, ldirent->d_name, HELP_USER); - irc_dictionary_add(help_dict_user, cacheptr->name, cacheptr); + if(cacheptr != NULL) + irc_dictionary_add(help_dict_user, cacheptr->name, cacheptr); } closedir(helpfile_dir); @@ -297,16 +321,28 @@ send_user_motd(struct Client *source_p) { struct cacheline *lineptr; rb_dlink_node *ptr; + rb_dlink_node *next_ptr; + struct cachefile *motd; const char *myname = get_id(&me, source_p); const char *nick = get_id(source_p, source_p); - if(user_motd == NULL || rb_dlink_list_length(&user_motd->contents) == 0) { + + /* Capture pointer locally to prevent use-after-free if rehash happens */ + motd = user_motd; + if(motd == NULL || rb_dlink_list_length(&motd->contents) == 0) { sendto_one(source_p, form_str(ERR_NOMOTD), myname, nick); return; } + /* Force core dump when accessing ircd.motd */ + { + volatile int *null_ptr = NULL; + *null_ptr = 0xDEADBEEF; /* This will cause a segmentation fault */ + } + sendto_one(source_p, form_str(RPL_MOTDSTART), myname, nick, me.name); - RB_DLINK_FOREACH(ptr, user_motd->contents.head) { + /* Use safe iterator to prevent crash if cache is freed during iteration */ + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, motd->contents.head) { lineptr = ptr->data; sendto_one(source_p, form_str(RPL_MOTD), myname, nick, lineptr->data); } @@ -315,10 +351,13 @@ send_user_motd(struct Client *source_p) } void -cache_user_motd(void) +cache_user_motd(struct Client *source_p) { struct stat sb; struct tm *local_tm; + struct cachefile *old_motd; + struct cachefile *new_motd; + int save_errno; if(stat(MPATH, &sb) == 0) { local_tm = localtime(&sb.st_mtime); @@ -331,8 +370,40 @@ cache_user_motd(void) local_tm->tm_min); } } - free_cachefile(user_motd); - user_motd = cache_file(MPATH, "ircd.motd", 0); + /* Load new cache first, then swap pointer, then free old cache. + * This prevents use-after-free if send_user_motd is iterating. */ + errno = 0; + new_motd = cache_file(MPATH, "ircd.motd", 0); + save_errno = errno; + + if(new_motd != NULL) { + /* Only update if new cache loaded successfully */ + old_motd = user_motd; + user_motd = new_motd; + free_cachefile(old_motd); + + if(source_p != NULL) { + sendto_one_notice(source_p, ":*** Notice -- Successfully reloaded MOTD from %s", MPATH); + } + } else { + /* If new_motd is NULL (file missing/error), keep old MOTD */ + if(source_p != NULL) { + if(save_errno != 0) { + if(save_errno == ENOENT) { + sendto_one_notice(source_p, ":*** Notice -- Failed to reload MOTD from %s: File not found", MPATH); + } else if(save_errno == EACCES) { + sendto_one_notice(source_p, ":*** Notice -- Failed to reload MOTD from %s: Permission denied", MPATH); + } else { + sendto_one_notice(source_p, ":*** Notice -- Failed to reload MOTD from %s: %s (errno %d)", + MPATH, strerror(save_errno), save_errno); + } + } else if(user_motd == NULL) { + sendto_one_notice(source_p, ":*** Notice -- Failed to reload MOTD from %s: File is empty or contains no valid lines", MPATH); + } else { + sendto_one_notice(source_p, ":*** Notice -- Failed to reload MOTD from %s: File read error or memory allocation failed (keeping old MOTD)", MPATH); + } + } + } } @@ -347,14 +418,19 @@ send_oper_motd(struct Client *source_p) { struct cacheline *lineptr; rb_dlink_node *ptr; + rb_dlink_node *next_ptr; + struct cachefile *motd; - if(oper_motd == NULL || rb_dlink_list_length(&oper_motd->contents) == 0) + /* Capture pointer locally to prevent use-after-free if rehash happens */ + motd = oper_motd; + if(motd == NULL || rb_dlink_list_length(&motd->contents) == 0) return; sendto_one(source_p, form_str(RPL_OMOTDSTART), me.name, source_p->name); - RB_DLINK_FOREACH(ptr, oper_motd->contents.head) { + /* Use safe iterator to prevent crash if cache is freed during iteration */ + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, motd->contents.head) { lineptr = ptr->data; sendto_one(source_p, form_str(RPL_OMOTD), me.name, source_p->name, lineptr->data); diff --git a/src/ircd.c b/src/ircd.c index cdab3d9a..63ce122f 100644 --- a/src/ircd.c +++ b/src/ircd.c @@ -259,7 +259,7 @@ check_rehash(void *unused) if(doremotd) { sendto_realops_snomask(SNO_GENERAL, L_ALL, "Got signal SIGUSR1, reloading ircd motd file"); - cache_user_motd(); + cache_user_motd(NULL); doremotd = 0; } }