diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 7125e5b04..5c1aebe88 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -59,7 +59,16 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.read((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 - // sanitise bad pref values + // sanitize loaded node_name to printable ASCII (defense against legacy invalid values) + { + char filtered[sizeof(_prefs->node_name)]; + StrHelper::filterToPrintableASCII(filtered, _prefs->node_name, sizeof(filtered)); + if (strcmp(filtered, _prefs->node_name) != 0) { + StrHelper::strncpy(_prefs->node_name, filtered, sizeof(_prefs->node_name)); + } + } + + // sanitise bad pref numeric values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); _prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0, 2.0f); _prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0, 2.0f); @@ -313,9 +322,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "Error, invalid key"); } } else if (memcmp(config, "name ", 5) == 0) { - StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); - savePrefs(); - strcpy(reply, "OK"); + const char* new_name = &config[5]; + size_t n = strlen(new_name); + if (n == 0 || n >= sizeof(_prefs->node_name)) { + strcpy(reply, "Error: name length must be 1-31"); + } else if (!StrHelper::isPrintableASCII(new_name)) { + strcpy(reply, "Error: name must be printable ASCII (0x20-0x7E)"); + } else { + StrHelper::strncpy(_prefs->node_name, new_name, sizeof(_prefs->node_name)); + savePrefs(); + strcpy(reply, "OK"); + } } else if (memcmp(config, "repeat ", 7) == 0) { _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; savePrefs(); diff --git a/src/helpers/ESP32Board.cpp b/src/helpers/ESP32Board.cpp index 4dce467cd..44452074f 100644 --- a/src/helpers/ESP32Board.cpp +++ b/src/helpers/ESP32Board.cpp @@ -10,16 +10,44 @@ #include +static void htmlEscape(char* dest, size_t dest_sz, const char* src) { + if (dest_sz == 0) return; + size_t i = 0; + for (; *src && i + 1 < dest_sz; ++src) { + const char* rep = nullptr; + switch (*src) { + case '&': rep = "&"; break; + case '<': rep = "<"; break; + case '>': rep = ">"; break; + case '"': rep = """; break; + case 39: rep = "'"; break; // '\'' + default: rep = nullptr; break; + } + if (rep) { + for (const char* p = rep; *p && i + 1 < dest_sz; ++p) dest[i++] = *p; + } else { + dest[i++] = *src; + } + } + dest[i] = 0; +} + bool ESP32Board::startOTAUpdate(const char* id, char reply[]) { WiFi.softAP("MeshCore-OTA", NULL); sprintf(reply, "Started: http://%s/update", WiFi.softAPIP().toString().c_str()); MESH_DEBUG_PRINTLN("startOTAUpdate: %s", reply); - static char id_buf[60]; - sprintf(id_buf, "%s (%s)", id, getManufacturerName()); - static char home_buf[90]; - sprintf(home_buf, "

Hi! I am a MeshCore Repeater. ID: %s

", id); + // HTML-escape dynamic values to avoid breaking the page with special chars + static char id_safe[128]; + static char man_safe[64]; + htmlEscape(id_safe, sizeof(id_safe), id); + htmlEscape(man_safe, sizeof(man_safe), getManufacturerName()); + + static char id_buf[200]; + snprintf(id_buf, sizeof(id_buf), "%s (%s)", id_safe, man_safe); + static char home_buf[240]; + snprintf(home_buf, sizeof(home_buf), "

Hi! I am a MeshCore Repeater. ID: %s

", id_safe); AsyncWebServer* server = new AsyncWebServer(80); diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index 0044fd286..c4f037b9e 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -19,6 +19,30 @@ void StrHelper::strzcpy(char* dest, const char* src, size_t buf_sz) { } } +bool StrHelper::isPrintableASCII(const char* s) { + if (!s) return false; + while (*s) { + unsigned char c = static_cast(*s++); + if (c < 0x20 || c > 0x7E) return false; + } + return true; +} + +void StrHelper::filterToPrintableASCII(char* dest, const char* src, size_t buf_sz) { + if (buf_sz == 0) return; + if (!src) { *dest = 0; return; } + while (buf_sz > 1 && *src) { + unsigned char c = static_cast(*src++); + if (c >= 0x20 && c <= 0x7E) { + *dest++ = (char)c; + } else { + *dest++ = '_'; + } + buf_sz--; + } + *dest = 0; +} + #include union int32_Float_t diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 3154766cd..d119890a4 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -11,5 +11,9 @@ class StrHelper { public: static void strncpy(char* dest, const char* src, size_t buf_sz); static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls + // Returns true if all characters are printable ASCII (0x20-0x7E) + static bool isPrintableASCII(const char* s); + // Copy src to dest, replacing non-printable ASCII bytes with '_'. Always null-terminates. + static void filterToPrintableASCII(char* dest, const char* src, size_t buf_sz); static const char* ftoa(float f); };