Skip to content

Commit 93e21a2

Browse files
committed
security/fix: Final review corrections for Ethernet runtime config
Security fixes: - IP validation: bounds checking for octets (0-255) - ETH.config() return value now checked with distinct logging - set ip 0.0.0.0 now enables DHCP (was rejected before) Documentation: - Fixed typo: 'thevalue' → 'the value' - Added missing: advert.zerohop command documentation - Clarified IP configuration behavior (DHCP, ETH_STATIC_IP fallback, reset to DHCP) All identified issues addressed or documented as out-of-scope. PR #2260 ready for maintainer review.
1 parent 3a5c1fb commit 93e21a2

11 files changed

Lines changed: 104 additions & 70 deletions

File tree

docs/cli_commands.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ This document provides an overview of CLI commands that can be sent to MeshCore
6464

6565
---
6666

67+
### Send a zero-hop advert
68+
**Usage:**
69+
- `advert.zerohop`
70+
71+
---
72+
6773
### Start an Over-The-Air (OTA) firmware update
6874
**Usage:**
6975
- `start ota`
@@ -792,7 +798,7 @@ region save
792798

793799
---
794800

795-
#### View or change thevalue of a sensor
801+
#### View or change the value of a sensor
796802
**Usage:**
797803
- `sensor get <key>`
798804
- `sensor set <key> <value>`
@@ -910,7 +916,7 @@ region save
910916

911917
**Default:** `0.0.0.0` (uses DHCP if not configured)
912918

913-
**Note:** Requires reboot to apply. If no valid IP is set, device will use DHCP at boot.
919+
**Note:** Requires reboot to apply. Set to `0.0.0.0` to revert to DHCP. If `ETH_STATIC_IP` is defined in build flags and DHCP fails, that compile-time address is used as fallback.
914920

915921
---
916922

examples/simple_repeater/main.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,10 @@ void setup() {
9999

100100
the_mesh.begin(fs);
101101

102-
the_mesh.begin(fs);
103-
104102
#ifdef USE_ETHERNET
105103
NodePrefs* prefs = the_mesh.getNodePrefs();
106104
if (prefs->eth_ip != 0) {
107-
board.reconfigureEthernet(prefs->eth_ip, prefs->eth_gateway, prefs->eth_subnet);
105+
board.reconfigureEthernet(prefs->eth_ip, prefs->eth_gateway, prefs->eth_subnet, prefs->eth_dns1);
108106
}
109107
#endif
110108

@@ -150,7 +148,7 @@ void loop() {
150148
}
151149

152150
the_mesh.loop();
153-
#if defined(ESP32) && defined(TCP_CONSOLE_PORT)
151+
#if defined(TCP_CONSOLE_PORT)
154152
tcp_console.loop(the_mesh);
155153
#endif
156154

examples/simple_room_server/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ void setup() {
8484
#ifdef USE_ETHERNET
8585
NodePrefs* prefs = the_mesh.getNodePrefs();
8686
if (prefs->eth_ip != 0) {
87-
board.reconfigureEthernet(prefs->eth_ip, prefs->eth_gateway, prefs->eth_subnet);
87+
board.reconfigureEthernet(prefs->eth_ip, prefs->eth_gateway, prefs->eth_subnet, prefs->eth_dns1);
8888
}
8989
#endif
9090

src/MeshCore.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class MainBoard {
6565
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }
6666
virtual uint8_t getShutdownReason() const { return 0; }
6767
virtual const char* getShutdownReasonString(uint8_t reason) { return "Not available"; }
68-
virtual void reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet) { /* no op */ }
68+
virtual void reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet, uint32_t dns1 = 0) { /* no op */ }
6969
};
7070

7171
/**

src/helpers/CommonCLI.cpp

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ static bool isValidName(const char *n) {
2323
}
2424

2525
// Helper functions for IP address conversion
26+
// Returns UINT32_MAX on parse error or out-of-range octet. Returns 0 for 0.0.0.0 (DHCP/clear).
2627
static uint32_t ipStringToUint32(const char* ip_str) {
27-
uint32_t ip = 0;
28-
uint8_t parts[4] = {0, 0, 0, 0};
29-
sscanf(ip_str, "%hhu.%hhu.%hhu.%hhu", &parts[0], &parts[1], &parts[2], &parts[3]);
30-
ip = ((uint32_t)parts[0] << 24) | ((uint32_t)parts[1] << 16) | ((uint32_t)parts[2] << 8) | parts[3];
31-
return ip;
28+
unsigned int a = 0, b = 0, c = 0, d = 0;
29+
if (sscanf(ip_str, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) return UINT32_MAX;
30+
if (a > 255 || b > 255 || c > 255 || d > 255) return UINT32_MAX;
31+
return ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d;
3232
}
3333

3434
static void uint32ToIPString(uint32_t ip, char* buffer, size_t size) {
@@ -134,14 +134,6 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
134134
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
135135
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
136136

137-
// sanitise bad ethernet pref values
138-
// If values are 0 (not set), they will be initialized from platformio.ini defaults at boot
139-
if (_prefs->eth_ip == 0) _prefs->eth_ip = 0; // 0 = use platformio.ini default
140-
if (_prefs->eth_gateway == 0) _prefs->eth_gateway = 0;
141-
if (_prefs->eth_subnet == 0) _prefs->eth_subnet = 0;
142-
if (_prefs->eth_dns1 == 0) _prefs->eth_dns1 = 0;
143-
if (_prefs->eth_dns2 == 0) _prefs->eth_dns2 = 0;
144-
145137
file.close();
146138
}
147139
}
@@ -310,7 +302,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
310302
// change admin password
311303
StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
312304
savePrefs();
313-
sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!!
305+
strcpy(reply, "OK - password changed");
314306
} else if (memcmp(command, "clear stats", 11) == 0) {
315307
_callbacks->clearStats();
316308
strcpy(reply, "(OK - stats reset)");
@@ -735,39 +727,39 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
735727
#ifdef USE_ETHERNET
736728
} else if (memcmp(config, "ip ", 3) == 0) {
737729
uint32_t ip = ipStringToUint32(&config[3]);
738-
if (ip != 0) {
730+
if (ip == UINT32_MAX) {
731+
strcpy(reply, "Error: invalid IP format");
732+
} else {
739733
_prefs->eth_ip = ip;
740734
savePrefs();
741-
strcpy(reply, "OK - reboot to apply");
742-
} else {
743-
strcpy(reply, "Error: invalid IP format");
735+
strcpy(reply, ip == 0 ? "OK - reboot to apply (will use DHCP)" : "OK - reboot to apply");
744736
}
745737
} else if (memcmp(config, "subnet ", 7) == 0) {
746738
uint32_t subnet = ipStringToUint32(&config[7]);
747-
if (subnet != 0) {
739+
if (subnet == UINT32_MAX) {
740+
strcpy(reply, "Error: invalid subnet format");
741+
} else {
748742
_prefs->eth_subnet = subnet;
749743
savePrefs();
750744
strcpy(reply, "OK - reboot to apply");
751-
} else {
752-
strcpy(reply, "Error: invalid subnet format");
753-
}
745+
}
754746
} else if (memcmp(config, "gw ", 3) == 0) {
755747
uint32_t gw = ipStringToUint32(&config[3]);
756-
if (gw != 0) {
748+
if (gw == UINT32_MAX) {
749+
strcpy(reply, "Error: invalid IP format");
750+
} else {
757751
_prefs->eth_gateway = gw;
758752
savePrefs();
759753
strcpy(reply, "OK - reboot to apply");
760-
} else {
761-
strcpy(reply, "Error: invalid IP format");
762754
}
763755
} else if (memcmp(config, "dns ", 4) == 0) {
764756
uint32_t dns = ipStringToUint32(&config[4]);
765-
if (dns != 0) {
757+
if (dns == UINT32_MAX) {
758+
strcpy(reply, "Error: invalid IP format");
759+
} else {
766760
_prefs->eth_dns1 = dns;
767761
savePrefs();
768762
strcpy(reply, "OK - reboot to apply");
769-
} else {
770-
strcpy(reply, "Error: invalid IP format");
771763
}
772764
#endif
773765
} else {

src/helpers/esp32/TCPConsole.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class TCPConsole {
3434
void disconnectClient(int i) {
3535
_clients[i].stop();
3636
_authenticated[i] = false;
37-
_cmd_buf[i][0] = 0;
37+
memset(_cmd_buf[i], 0, sizeof(_cmd_buf[i]));
3838
_cmd_len[i] = 0;
3939
}
4040

@@ -43,7 +43,7 @@ class TCPConsole {
4343
: _server(TCP_CONSOLE_PORT), _prefs(prefs) {
4444
for (int i = 0; i < TCP_CONSOLE_MAX_CLIENTS; i++) {
4545
_authenticated[i] = false;
46-
_cmd_buf[i][0] = 0;
46+
memset(_cmd_buf[i], 0, sizeof(_cmd_buf[i]));
4747
_cmd_len[i] = 0;
4848
_last_active[i] = 0;
4949
}
@@ -62,17 +62,23 @@ class TCPConsole {
6262
// Accept new clients
6363
WiFiClient newClient = _server.available();
6464
if (newClient) {
65+
bool found = false;
6566
for (int i = 0; i < TCP_CONSOLE_MAX_CLIENTS; i++) {
6667
if (!_clients[i] || !_clients[i].connected()) {
6768
_clients[i] = newClient;
6869
_authenticated[i] = false;
69-
_cmd_buf[i][0] = 0;
70+
memset(_cmd_buf[i], 0, sizeof(_cmd_buf[i]));
7071
_cmd_len[i] = 0;
7172
_last_active[i] = millis();
7273
sendToClient(i, "MeshCore Console\r\nPassword: ");
74+
found = true;
7375
break;
7476
}
7577
}
78+
if (!found) {
79+
newClient.print("Server busy. Try again later.\r\n");
80+
newClient.stop();
81+
}
7682
}
7783

7884
// Handle connected clients
@@ -109,8 +115,11 @@ class TCPConsole {
109115
_cmd_buf[i][_cmd_len[i]] = 0;
110116

111117
if (!_authenticated[i]) {
112-
// Authentication — always read from live NodePrefs, not compile-time constant
113-
if (_prefs != nullptr && strcmp(_cmd_buf[i], _prefs->password) == 0) {
118+
// Compare full password field with memcmp to avoid short-circuit timing
119+
bool ok = _prefs != nullptr &&
120+
_cmd_len[i] == (int)strnlen(_prefs->password, sizeof(_prefs->password)) &&
121+
memcmp(_cmd_buf[i], _prefs->password, sizeof(_prefs->password)) == 0;
122+
if (ok) {
114123
_authenticated[i] = true;
115124
char welcome[80];
116125
snprintf(welcome, sizeof(welcome), "Welcome to %s console.\r\n> ", _prefs->node_name);
@@ -134,7 +143,7 @@ class TCPConsole {
134143
sendToClient(i, "> ");
135144
}
136145

137-
_cmd_buf[i][0] = 0;
146+
memset(_cmd_buf[i], 0, sizeof(_cmd_buf[i]));
138147
_cmd_len[i] = 0;
139148
}
140149
}

src/helpers/esp32/TEthEliteBoard.cpp

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "TEthEliteBoard.h"
99
#include "target.h"
1010
#include "helpers/ui/MomentaryButton.h"
11+
#include <esp_task_wdt.h>
1112

1213
extern MomentaryButton user_btn;
1314

@@ -130,30 +131,26 @@ void TEthEliteBoard::startEthernet() {
130131
ETH.begin(ETH_PHY_W5500, ETH_ADDR, ETH_CS, ETH_INT, -1, SPI2_HOST, ETH_SCLK, ETH_MISO, ETH_MOSI);
131132
delay(100);
132133

133-
#ifndef USE_ETHERNET
134-
// Used only if USE_ETHERNET is not enabled
135-
#ifdef ETH_STATIC_IP
136-
IPAddress ip(ETH_STATIC_IP);
137-
IPAddress gw(ETH_GATEWAY);
138-
IPAddress mask(ETH_SUBNET);
139-
IPAddress dns(ETH_DNS);
140-
ETH.config(ip, gw, mask, dns);
141-
#endif
142-
#endif
143-
144134
unsigned long t0 = millis();
145135
while (!ETH.linkUp() && millis() - t0 < 5000) {
136+
esp_task_wdt_reset();
146137
delay(100);
147138
}
148139

149140
t0 = millis();
150141
while (ETH.localIP() == IPAddress(0, 0, 0, 0) && millis() - t0 < 5000) {
142+
esp_task_wdt_reset();
151143
delay(100);
152144
}
153145

154146
if (ETH.localIP() == IPAddress(0, 0, 0, 0)) {
147+
#ifdef ETH_STATIC_IP
148+
Serial.println("DHCP timeout, using static IP from build flags");
149+
ETH.config(IPAddress(ETH_STATIC_IP), IPAddress(ETH_GATEWAY), IPAddress(ETH_SUBNET), IPAddress(ETH_DNS));
150+
#else
155151
Serial.println("DHCP timeout, using fallback IP");
156152
ETH.config(IPAddress(192, 168, 4, 2), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0));
153+
#endif
157154
}
158155

159156
eth_local_ip = ETH.localIP().toString(); // save IP for OTA use
@@ -164,30 +161,40 @@ void TEthEliteBoard::startEthernet() {
164161
Serial.print("ETH IP "); Serial.println(ETH.localIP());
165162
Serial.println(ETH.linkUp() ? "ETH LINK UP" : "ETH LINK DOWN");
166163
}
167-
void TEthEliteBoard::reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet) {
164+
void TEthEliteBoard::reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet, uint32_t dns1) {
168165
if (ip != 0) {
169166
uint8_t b1 = (ip >> 24) & 0xFF;
170167
uint8_t b2 = (ip >> 16) & 0xFF;
171168
uint8_t b3 = (ip >> 8) & 0xFF;
172169
uint8_t b4 = ip & 0xFF;
173-
170+
174171
uint8_t gw1 = (gw >> 24) & 0xFF;
175172
uint8_t gw2 = (gw >> 16) & 0xFF;
176173
uint8_t gw3 = (gw >> 8) & 0xFF;
177174
uint8_t gw4 = gw & 0xFF;
178-
175+
179176
uint8_t sub1 = (subnet >> 24) & 0xFF;
180177
uint8_t sub2 = (subnet >> 16) & 0xFF;
181178
uint8_t sub3 = (subnet >> 8) & 0xFF;
182179
uint8_t sub4 = subnet & 0xFF;
183-
184-
ETH.config(
180+
181+
uint8_t dns_1 = (dns1 >> 24) & 0xFF;
182+
uint8_t dns_2 = (dns1 >> 16) & 0xFF;
183+
uint8_t dns_3 = (dns1 >> 8) & 0xFF;
184+
uint8_t dns_4 = dns1 & 0xFF;
185+
186+
bool ok = ETH.config(
185187
IPAddress(b1, b2, b3, b4),
186188
IPAddress(gw1, gw2, gw3, gw4),
187-
IPAddress(sub1, sub2, sub3, sub4)
189+
IPAddress(sub1, sub2, sub3, sub4),
190+
IPAddress(dns_1, dns_2, dns_3, dns_4)
188191
);
189-
Serial.printf("ETH reconfigured to %d.%d.%d.%d\n", b1, b2, b3, b4);
190-
eth_local_ip = ETH.localIP().toString(); // Aggiorna IP locale per OTA
192+
if (ok) {
193+
Serial.printf("ETH reconfigured to %d.%d.%d.%d\n", b1, b2, b3, b4);
194+
} else {
195+
Serial.println("ETH reconfigure failed");
196+
}
197+
eth_local_ip = ETH.localIP().toString();
191198
}
192199
}
193200
#endif

src/helpers/esp32/TEthEliteBoard_SX1262.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class TEthEliteBoard : public ESP32Board {
7373
void startNetwork();
7474
void startEthernet();
7575
void startWifi();
76-
void reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet);
76+
void reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet, uint32_t dns1 = 0);
7777

7878
void enterDeepSleep(uint32_t secs, int pin_wake_btn) {
7979
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);

src/helpers/esp32/TEthEliteBoard_SX1276.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class TEthEliteBoard : public ESP32Board {
7373
void startNetwork();
7474
void startEthernet();
7575
void startWifi();
76-
void reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet);
76+
void reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet, uint32_t dns1 = 0);
7777

7878
void enterDeepSleep(uint32_t secs, int pin_wake_btn) {
7979
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);

0 commit comments

Comments
 (0)