From 48d4fde94186e591bf070432a6b15bc0c8a88d7c Mon Sep 17 00:00:00 2001 From: Adam Hockley Date: Mon, 17 Nov 2025 08:12:27 +0000 Subject: [PATCH 1/5] Update m_rehash.c test the rehash now Signed-off-by: Adam Hockley --- modules/m_rehash.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/m_rehash.c b/modules/m_rehash.c index d8730d7e..e6c7c483 100644 --- a/modules/m_rehash.c +++ b/modules/m_rehash.c @@ -44,6 +44,7 @@ #include "hash.h" #include "cache.h" #include "sslproc.h" + static int mo_rehash(struct Client *, struct Client *, int, const char **); static int me_rehash(struct Client *, struct Client *, int, const char **); @@ -102,6 +103,7 @@ rehash_motd(struct Client *source_p) { struct stat sb; struct tm *local_tm; + char motd_path[PATH_MAX]; sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "%s is forcing re-reading of MOTD file", @@ -112,7 +114,11 @@ rehash_motd(struct Client *source_p) free_cachefile(user_motd); user_motd = cache_file(MPATH, "ircd.motd", 0); - if(stat(MPATH, &sb) == 0) { + /* Construct the full file path for stat() */ + rb_snprintf(motd_path, sizeof(motd_path), "%s/ircd.motd", MPATH); + + /* Get the file's modification time (not the directory's) */ + if(stat(motd_path, &sb) == 0) { local_tm = localtime(&sb.st_mtime); if(local_tm != NULL) { From 45e8fdd63459d3458306f2f74bed42e192114876 Mon Sep 17 00:00:00 2001 From: Adam Hockley Date: Mon, 17 Nov 2025 09:16:22 +0000 Subject: [PATCH 2/5] Fix crash in /rehash motd - correct order of operations Fixed the order to match cache_user_motd() - stat() must be called before free_cachefile() to avoid potential race conditions or crashes. The stat() call should happen first to get file metadata, then free and reload the cache. --- modules/m_rehash.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/modules/m_rehash.c b/modules/m_rehash.c index e6c7c483..75eaacbc 100644 --- a/modules/m_rehash.c +++ b/modules/m_rehash.c @@ -103,7 +103,6 @@ rehash_motd(struct Client *source_p) { struct stat sb; struct tm *local_tm; - char motd_path[PATH_MAX]; sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, "%s is forcing re-reading of MOTD file", @@ -111,14 +110,7 @@ rehash_motd(struct Client *source_p) if (!MyConnect(source_p)) remote_rehash_oper_p = source_p; - free_cachefile(user_motd); - user_motd = cache_file(MPATH, "ircd.motd", 0); - - /* Construct the full file path for stat() */ - rb_snprintf(motd_path, sizeof(motd_path), "%s/ircd.motd", MPATH); - - /* Get the file's modification time (not the directory's) */ - if(stat(motd_path, &sb) == 0) { + if(stat(MPATH, &sb) == 0) { local_tm = localtime(&sb.st_mtime); if(local_tm != NULL) { @@ -129,6 +121,8 @@ rehash_motd(struct Client *source_p) local_tm->tm_min); } } + free_cachefile(user_motd); + user_motd = cache_file(MPATH, "ircd.motd", 0); } static void From 102cfded529761c62e010aeb4e2b32507ee085e1 Mon Sep 17 00:00:00 2001 From: Adam Hockley Date: Mon, 17 Nov 2025 09:28:29 +0000 Subject: [PATCH 3/5] Replace m_rehash.c with requested version --- modules/m_rehash.c | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/modules/m_rehash.c b/modules/m_rehash.c index 75eaacbc..d04feabd 100644 --- a/modules/m_rehash.c +++ b/modules/m_rehash.c @@ -44,7 +44,6 @@ #include "hash.h" #include "cache.h" #include "sslproc.h" - static int mo_rehash(struct Client *, struct Client *, int, const char **); static int me_rehash(struct Client *, struct Client *, int, const char **); @@ -87,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(); } @@ -101,28 +98,26 @@ 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; - 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); - } - } - free_cachefile(user_motd); - user_motd = cache_file(MPATH, "ircd.motd", 0); + cache_user_motd(); +} + +static void +rehash_rules(struct Client *source_p) +{ + 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; + + free_cachefile(user_rules); + user_rules = cache_file(RPATH, "ircd.rules", 0); } static void @@ -299,6 +294,7 @@ static struct hash_commands rehash_commands[] = { {"DNS", rehash_dns }, {"SSLD", rehash_ssld }, {"MOTD", rehash_motd }, + {"RULES", rehash_rules }, {"OMOTD", rehash_omotd }, {"TKLINES", rehash_tklines }, {"TDLINES", rehash_tdlines }, From c43584f020875a1edb6f8a73e29bd25fdcc9daa5 Mon Sep 17 00:00:00 2001 From: Adam Hockley Date: Mon, 17 Nov 2025 09:34:09 +0000 Subject: [PATCH 4/5] Remove rehash_rules entry --- modules/m_rehash.c | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/m_rehash.c b/modules/m_rehash.c index d04feabd..dd0d4513 100644 --- a/modules/m_rehash.c +++ b/modules/m_rehash.c @@ -294,7 +294,6 @@ static struct hash_commands rehash_commands[] = { {"DNS", rehash_dns }, {"SSLD", rehash_ssld }, {"MOTD", rehash_motd }, - {"RULES", rehash_rules }, {"OMOTD", rehash_omotd }, {"TKLINES", rehash_tklines }, {"TDLINES", rehash_tdlines }, From 9e4adea283d2cc2289a3dd11c132e9dceff559e4 Mon Sep 17 00:00:00 2001 From: Adam Hockley Date: Mon, 17 Nov 2025 10:11:46 +0000 Subject: [PATCH 5/5] Fix MOTD rehash crash and add error reporting - Fix use-after-free crash in free_cachefile (removed invalid rb_free on embedded data) - Fix race condition in send_user_motd/send_oper_motd (use safe iterators, capture pointers locally) - Fix unsafe pointer swap in cache_user_motd/rehash_omotd (load new cache first, then swap) - Add proper error handling in cache_file (check malloc failures, file read errors, emptyline NULL check) - Add error reporting to cache_user_motd (show errors in chat when /rehash motd fails) - Preserve old MOTD if new one fails to load - Fix same issues in rehash_rules --- include/cache.h | 2 +- modules/m_rehash.c | 32 ++++++++++++++--- src/cache.c | 86 ++++++++++++++++++++++++++++++++++++++++------ src/ircd.c | 2 +- 4 files changed, 104 insertions(+), 18 deletions(-) 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/modules/m_rehash.c b/modules/m_rehash.c index dd0d4513..589db509 100644 --- a/modules/m_rehash.c +++ b/modules/m_rehash.c @@ -104,33 +104,55 @@ rehash_motd(struct Client *source_p) if (!MyConnect(source_p)) remote_rehash_oper_p = source_p; - cache_user_motd(); + cache_user_motd(source_p); } static void rehash_rules(struct Client *source_p) { + struct cachefile *old_rules; + struct cachefile *new_rules; + 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; - free_cachefile(user_rules); - user_rules = cache_file(RPATH, "ircd.rules", 0); + /* 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..73258f25 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)) { @@ -199,7 +218,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); @@ -297,16 +315,22 @@ 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; } 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 +339,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 +358,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 +406,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; } }