New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
esp8266: strncpy() segfaults on empty password in webrepl's 'port_config.py' #2024
Comments
I think I know what is going on here. strncpy() is in the "Internal ROM" according to the ESP memory map:
...and ROM can't access SPI Flash on ESP (not at usual Borrowing --- a/extmod/modwebrepl.c
+++ b/extmod/modwebrepl.c
@@ -284,11 +284,28 @@ STATIC mp_uint_t webrepl_write(mp_obj_t self_in, const void *buf, mp_uint_t size
return stream_p->write(self->sock, buf, size, errcode);
}
+char* ICACHE_FLASH_ATTR strncpy_P(char* dest, const char* src, size_t size) {
+ const char* read = src;
+ char* write = dest;
+ char ch = '.';
+ while (size > 0 && ch != '\0') {
+ ch = *read++;
+ *write++ = ch;
+ size--;
+ }
+ return dest;
+}
+
STATIC mp_obj_t webrepl_set_password(mp_obj_t passwd_in) {
+ printf("in webrepl_set_password()\n");
const char *passwd = mp_obj_str_get_str(passwd_in);
+ printf("*passwd is '%s'\n", passwd);
+ printf("*passwd is at 0x%08x\n", (unsigned int)(void *)passwd);
- strncpy(webrepl_passwd, passwd, sizeof(webrepl_passwd));
+ strncpy_P(webrepl_passwd, passwd, sizeof(webrepl_passwd));
+ printf("strncpy_P() returned\n");
return mp_const_none;
}
+ ...seem to fix this:
|
No, it's initial implementation which is gets the main flow right, but leaves out gazillion of corner cases.
No, it's real hardware and it has real and pretty simple constraints - on access alignment and size. Good reference on background of it I can give is the essay which was published as a part of Kickstarter campaign: https://www.kickstarter.com/projects/214379695/micropython-on-the-esp8266-beautifully-easy-iot/posts/1501224 Overall, great research and debugging, I myself was confused how this may happen for this case, your dumps made it all clear, excellent work! @dpgeorge : So, we were pleasantly surprised that -mforce-l32 works even when you wouldn't think it does, and it's 2nd time I got bitten by it "unexpectedly" not working, dealing with this webrepl password prompt. First time, I passed stuff from .rodata to lwip, it's not built with -mforce-l32, so kaboom. Here, it's even more confusing. First thought about strncpy() in libc which is again not built with -mforce-l32. But we build with -nostdlib. And then - destination buffer is in dRAM, source string is in dRAM, what can go wrong? Well, ROMmed qstr's! And another unclear q how BootROM funcs are involved? Well, libc's not used, no strncpy() impl in uPy codebase, then lowest-prio PROVIDE in .ld actually kicks in. So, I guess the least resistance workaround is do what @aexaey suggests and use own strncpy impl. My initial inclination was to name it safe_strncpy() and add to esp_mphal.c, but then the issue may happen again and again. So, maybe add to lib/libc/string0.c? Well, the easiest way would be to replaces strncpy() with memcpy()... |
Ok, we have enough stuff in string0.c, so no big deal add another func to be --gc-sections'ed if not needed. Done. |
@pfalcon I would suggest not to use strncpy at all (it's not used in any of our code at all, except now in webrepl...). It's too error prone, and the implementation you provided is wrong (no null should be added if there is no room at the end). So I'd recode the webrepl_set_password function to use memcpy and then explicitly add a null terminator. |
LOL, live and learn wonderful C standards we have! Will revert and redo. |
Initial setup of webrepl would happily accept empty password (which is fine, and by design, I suppose?), but then
_webrepl.password()
would segfault onstrncpy()
.Here is instrumentation added:
Here is log of a failing test on the commit 1f0dfe3 + instrumentation above:
Same with non-empty password succeeds as expected:
Looking at the
*passwd is at
line above, it appears that normal password is stored at0x3fff0175
, i.e. "ETS system data RAM" section, while empty password - under a pointer to0x4026ab11
, i.e. "SPI flash" memory range, according to [1]. Exact same address is to be seen in "excvaddr" which I would guess implies that direct memory access to SPI could sometimes be denied - artifact of ESP-specific paging/caching?Actual data stored on
0x4026ab11
appears to be'\0'
, i.e. empty C string, which sort of makes sense:Strangely enough, accessing very same data from python code works just fine - maybe because interpreter has a special case for "empty string constant" that it compared by address without actually accessing it?
[1] https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map
The text was updated successfully, but these errors were encountered: