Skip to content

Commit abc5cff

Browse files
committed
Patch eap.o memory leak
WiFi Enterprise option can leak up to 3 allocations per connect/disconnect cycle: anonymous Identity, password, and some unidentified allocation. This solution patches eap.o from libwpa2 to call a special 2 part wrapper instead of vPortFree for cleanup. Corrected typos and adjusted tabs in script.
1 parent 2de142b commit abc5cff

File tree

12 files changed

+296
-10
lines changed

12 files changed

+296
-10
lines changed

Diff for: cores/esp8266/coredecls.h

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ void esp_schedule();
2121
void esp_yield();
2222
void tune_timeshift64 (uint64_t now_us);
2323
void disable_extra4k_at_link_time (void) __attribute__((noinline));
24+
void enable_wifi_enterprise_patch(void) __attribute__((noinline));
2425
bool sntp_set_timezone_in_seconds(int32_t timezone);
2526
void __disableWiFiAtBootTime (void) __attribute__((noinline));
2627
void __real_system_restart_local() __attribute__((noreturn));

Diff for: cores/esp8266/heap.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55

66
#include <stdlib.h>
77
#include "umm_malloc/umm_malloc.h"
8-
extern "C" size_t umm_umul_sat(const size_t a, const size_t b);;
8+
extern "C" size_t umm_umul_sat(const size_t a, const size_t b);
9+
10+
// z2EapFree: See wpa2_eap_patch.cpp for details
11+
extern "C" void z2EapFree(void *ptr, const char* file, int line) __attribute__((weak, alias("vPortFree"), nothrow));
12+
// I don't understand all the compiler noise around this alias.
13+
// Adding "__attribute__ ((nothrow))" seems to resolve the issue.
14+
// This may be relevant: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81824
915

1016
// Need FORCE_ALWAYS_INLINE to put HeapSelect class constructor/deconstructor in IRAM
1117
#define FORCE_ALWAYS_INLINE_HEAP_SELECT

Diff for: cores/esp8266/wpa2_eap_patch.cpp

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* To implement this patch, the SDK module `eap.o` from archive `libwpa2.a` must
3+
* be patched to call `z2EapFree` instead of `vPortFree`. This limits extending
4+
* the execution time of vPortFree to that module only. Not impacting other
5+
* modules.
6+
*
7+
*/
8+
9+
#include <string.h>
10+
#include <ets_sys.h>
11+
#include <pgmspace.h>
12+
#include "coredecls.h"
13+
#if 0
14+
#include "esp8266_undocumented.h"
15+
#define DEBUG_PRINTF ets_uart_printf
16+
#else
17+
#define DEBUG_PRINTF(...)
18+
#endif
19+
20+
extern "C" {
21+
22+
// extern "C" void z2EapFree(void *ptr, const char* file, int line) __attribute__((weak, alias("vPortFree")));
23+
24+
/*
25+
* Limited 2-part wrapper for vPortFree calls made in SDK module `eap.o` from
26+
* archive `libwpa2.a`.
27+
*
28+
* vPortFree calls from eap.o are monitored for calls from line 799. This is
29+
* the location of the memory leak. At entry register a12 contains the structure
30+
* address which has the addresses of the allocations that will be leaked.
31+
*
32+
* Part 1 of this wrapper, z2EapFree, appends the value of register a12 as a
33+
* 4th argument to part2 of this wrapper, patch_wpa2_eap_vPortFree_a12(). Which
34+
* in turn checks and frees the additional allocations, that would have been
35+
* lost.
36+
*
37+
* extern "C" z2EapFree(void*);
38+
*/
39+
40+
/*
41+
* Part 1 of Limited vPortFree Wrapper
42+
*/
43+
asm(
44+
// ".section .iram.text.z2EapFree,\"ax\",@progbits\n\t"
45+
// Since all the possible callers in eap.o are in sections starting with
46+
// .text and not .iram.text we should be safe putting these wrappers in .text.
47+
".section .text.z2EapFree,\"ax\",@progbits\n\t"
48+
".literal_position\n\t"
49+
".literal .patch_wpa2_eap_vPortFree_a12, patch_wpa2_eap_vPortFree_a12\n\t"
50+
".align 4\n\t"
51+
".global z2EapFree\n\t"
52+
".type z2EapFree, @function\n\t"
53+
"\n"
54+
"z2EapFree:\n\t"
55+
"addi a1, a1, -16\n\t"
56+
"s32i a0, a1, 0\n\t"
57+
"mov a5, a12\n\t"
58+
"l32r a0, .patch_wpa2_eap_vPortFree_a12\n\t"
59+
"callx0 a0\n\t"
60+
"l32i a0, a1, 0\n\t"
61+
"addi a1, a1, 16\n\t"
62+
"ret\n\t"
63+
".size z2EapFree, .-z2EapFree\n\t"
64+
);
65+
66+
/*
67+
* While some insight can be gained from the ESP32 repo for this structure.
68+
* It does not match exactly. This alternate structure focuses on correct offset
69+
* rather than trying to exactly reconstruct the original labels.
70+
*/
71+
struct StateMachine { // size 200 bytes
72+
void* beforeConfig[16];
73+
void* config[26];
74+
// 0 - mov a2, a12, 64 // username / Identity
75+
// 1 - mov a2, a12, 68
76+
// 2 - mov a2, a12, 72 // anonymous Identity
77+
// 3 - mov a2, a12, 76
78+
// 4 - mov a2, a12, 80 // password
79+
// 21 - mov a2, a12, 148 // ??
80+
void* afterConfig[8];
81+
};
82+
83+
/*
84+
* Part 2 of Limited vPortFree Wrapper
85+
*
86+
* Presently, all SDKs have the same memory leaks in the same module at the
87+
* same line.
88+
*/
89+
void patch_wpa2_eap_vPortFree_a12(void *ptr, const char* file, int line, void* a12) {
90+
if (799 == line) {
91+
struct StateMachine* sm = (struct StateMachine*)a12;
92+
if (ptr == sm->config[0]) {
93+
// Fix leaky frunction - eap.o only frees one out of 4 config items
94+
// finish the other 3 first
95+
vPortFree(sm->config[2], file, line);
96+
vPortFree(sm->config[4], file, line);
97+
vPortFree(sm->config[21], file, line);
98+
// ptr is sm->config[0], let fall through handle it
99+
}
100+
DEBUG_PRINTF("\nz2EapFree/vPortFree patch working\n");
101+
}
102+
vPortFree(ptr, file, line);
103+
}
104+
105+
};
106+
107+
/*
108+
* This will minimize code space for non-wifi enterprise sketches which do not
109+
* need the patch and disable_extra4k_at_link_time().
110+
*/
111+
void enable_wifi_enterprise_patch(void) {
112+
/*
113+
* Calling this from setup or anywhere ensures that the patch code is
114+
* included in the build.
115+
*
116+
* Also, WiFi Enterprise uses a lot of system stack space and may crash
117+
* unless we:
118+
*/
119+
disable_extra4k_at_link_time();
120+
}

Diff for: tools/sdk/lib/NONOSDK221/libwpa2.a

0 Bytes
Binary file not shown.

Diff for: tools/sdk/lib/NONOSDK22x_190313/libwpa2.a

0 Bytes
Binary file not shown.

Diff for: tools/sdk/lib/NONOSDK22x_190703/libwpa2.a

0 Bytes
Binary file not shown.

Diff for: tools/sdk/lib/NONOSDK22x_191024/libwpa2.a

0 Bytes
Binary file not shown.

Diff for: tools/sdk/lib/NONOSDK22x_191105/libwpa2.a

0 Bytes
Binary file not shown.

Diff for: tools/sdk/lib/NONOSDK22x_191122/libwpa2.a

0 Bytes
Binary file not shown.

Diff for: tools/sdk/lib/NONOSDK3V0/libwpa2.a

0 Bytes
Binary file not shown.

Diff for: tools/sdk/lib/eval_fix_sdks.sh

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/bin/bash
2+
# set -e
3+
4+
add_path_ifexist() {
5+
if [[ -d $1 ]]; then
6+
export PATH=$( realpath $1 ):$PATH
7+
return 0
8+
fi
9+
return 1
10+
}
11+
12+
if ! which xtensa-lx106-elf-ar | grep "tools/xtensa-lx106-elf/bin" >>/dev/null; then
13+
add_path_ifexist "../../../xtensa-lx106-elf/bin" || add_path_ifexist "../../xtensa-lx106-elf/bin"
14+
fi
15+
16+
17+
list_sdks() {
18+
cat <<EOF
19+
NONOSDK22x_190313
20+
NONOSDK22x_190703
21+
NONOSDK22x_191024
22+
NONOSDK22x_191105
23+
NONOSDK22x_191122
24+
NONOSDK221
25+
NONOSDK3V0
26+
EOF
27+
}
28+
29+
remove_ifexist() {
30+
[[ -f $1 ]] && rm $1
31+
}
32+
33+
cleanup() {
34+
remove_ifexist old.txt
35+
remove_ifexist old2.txt
36+
remove_ifexist new.txt
37+
for sdk in `list_sdks`; do
38+
remove_ifexist $sdk/eap.o
39+
done
40+
}
41+
42+
unasm() {
43+
xtensa-lx106-elf-objdump -d $*
44+
}
45+
46+
analyze() {
47+
cleanup
48+
49+
for sdk in `list_sdks`; do
50+
pushd $sdk
51+
xtensa-lx106-elf-ar x libwpa2.a eap.o
52+
popd
53+
done
54+
echo ""
55+
56+
find . -name eap.o -exec md5sum {} \; | sort
57+
echo ""
58+
59+
unset prev_sdk
60+
for sdk in `list_sdks`; do
61+
unasm -j ".text.eap_peer_config_deinit" ${sdk}/eap.o >new.txt
62+
if [[ -f old.txt ]]; then
63+
echo "eap_peer_config_deinit: diff $prev_sdk $sdk"
64+
diff old.txt new.txt
65+
echo ""
66+
fi
67+
mv new.txt old.txt
68+
prev_sdk=${sdk}
69+
done
70+
71+
unset prev_sdk
72+
for sdk in `list_sdks`; do
73+
unasm -j ".text.wpa2_sm_rx_eapol" ${sdk}/eap.o >new.txt
74+
if [[ -f old2.txt ]]; then
75+
echo "wpa2_sm_rx_eapol: diff $prev_sdk $sdk"
76+
diff old2.txt new.txt
77+
echo ""
78+
fi
79+
mv new.txt old2.txt
80+
prev_sdk=${sdk}
81+
done
82+
83+
cleanup
84+
}
85+
86+
87+
patch_all() {
88+
for sdk in `list_sdks`; do
89+
pushd $sdk
90+
../fix_sdk_libs.sh
91+
popd
92+
done
93+
}
94+
95+
# analyze
96+
patch_all
97+
exit 0

Diff for: tools/sdk/lib/fix_sdk_libs.sh

+71-9
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,98 @@
11
#!/bin/bash
22
set -e
33

4-
export PATH=../../xtensa-lx106-elf/bin:$PATH
4+
add_path_ifexist() {
5+
if [[ -d $1 ]]; then
6+
export PATH=$( realpath $1 ):$PATH
7+
return 0
8+
fi
9+
return 1
10+
}
11+
12+
if ! which xtensa-lx106-elf-ar | grep "tools/xtensa-lx106-elf/bin" >>/dev/null; then
13+
add_path_ifexist "../../../xtensa-lx106-elf/bin" || add_path_ifexist "../../xtensa-lx106-elf/bin"
14+
fi
15+
WORK_SPACE=${PWD}
16+
517
VERSION=$(basename ${PWD})
618

719
addSymbol_system_func1() {
8-
ADDRESS=$1
9-
xtensa-lx106-elf-objcopy --add-symbol system_func1=.irom0.text:${ADDRESS},function,global user_interface.o
20+
if ! xtensa-lx106-elf-nm user_interface.o | grep -q " T system_func1"; then # Don't add symbol if it already exists
21+
ADDRESS=$1
22+
xtensa-lx106-elf-objcopy --add-symbol system_func1=.irom0.text:${ADDRESS},function,global user_interface.o
23+
fi
1024
}
1125

26+
patchFile() {
27+
FILE=$1
28+
ADDRESS=$2 # DO NOT PASS AS HEX!
29+
LENGTH=$3 # DO NOT PASS AS HEX!
30+
EXPECTED=$4
31+
REPLACEWITH=$5
32+
if [[ "$(dd if=$FILE bs=1 count=$LENGTH skip=$ADDRESS status=none | base64 -w0)" = "$EXPECTED" ]]; then
33+
echo "Patching $VERSION $1 ..."
34+
echo $5 | base64 -d | dd of=$FILE bs=1 count=$LENGTH seek=$ADDRESS conv=notrunc
35+
elif ! [[ "$(dd if=$FILE bs=1 count=$LENGTH skip=$ADDRESS status=none | base64 -w0)" = "$REPLACEWITH" ]]; then
36+
echo "PATCH FAILED!"
37+
echo "dd if=$FILE bs=1 count=$LENGTH skip=$ADDRESS status=none | base64 -w0"
38+
dd if=$FILE bs=1 count=$LENGTH skip=$ADDRESS status=none | hexdump -C
39+
dd if=$FILE bs=1 count=$LENGTH skip=$ADDRESS status=none | base64 -w0
40+
echo ""
41+
exit 0
42+
fi
43+
}
44+
45+
# # xtensa-lx106-elf-ar x libwpa2.a eap.o
46+
if [[ "--shell" == "$1" ]]; then
47+
# need to poke around a bit
48+
bash --rcfile <(echo ". ~/.bashrc; cd ${WORK_SPACE}")
49+
exit 0
50+
fi
51+
52+
if [[ ! -f libmain.a ]]; then
53+
echo -e "\n\n*** Archive libmain.a is missing ***\n\n"
54+
exit 1
55+
fi
1256

1357
# Remove mem_manager.o from libmain.a to use custom heap implementation,
1458
# and time.o to fix redefinition of time-related functions:
1559
xtensa-lx106-elf-ar d libmain.a mem_manager.o
1660
xtensa-lx106-elf-ar d libmain.a time.o
1761

62+
# Patch WPA2-Enterprise double-free
63+
xtensa-lx106-elf-ar x libwpa2.a eap.o
64+
eapcs=$(sha256sum eap.o | awk '{print $1}')
65+
1866
# Rename `hostname` and `default_hostname` symbols:
1967
xtensa-lx106-elf-ar x libmain.a eagle_lwip_if.o user_interface.o
20-
xtensa-lx106-elf-objcopy --redefine-sym hostname=wifi_station_hostname user_interface.o
21-
xtensa-lx106-elf-objcopy --redefine-sym hostname=wifi_station_hostname eagle_lwip_if.o
22-
xtensa-lx106-elf-objcopy --redefine-sym default_hostname=wifi_station_default_hostname user_interface.o
23-
xtensa-lx106-elf-objcopy --redefine-sym default_hostname=wifi_station_default_hostname eagle_lwip_if.o
68+
lwipcs=$(sha256sum eagle_lwip_if.o | awk '{print $1}')
69+
uics=$(sha256sum user_interface.o | awk '{print $1}')
70+
xtensa-lx106-elf-objcopy --redefine-sym hostname=wifi_station_hostname user_interface.o
71+
xtensa-lx106-elf-objcopy --redefine-sym hostname=wifi_station_hostname eagle_lwip_if.o
72+
xtensa-lx106-elf-objcopy --redefine-sym default_hostname=wifi_station_default_hostname user_interface.o
73+
xtensa-lx106-elf-objcopy --redefine-sym default_hostname=wifi_station_default_hostname eagle_lwip_if.o
74+
2475

2576
if [[ ${VERSION} == "NONOSDK221" ]]; then
2677
addSymbol_system_func1 "0x60"
78+
patchFile "eap.o" "3055" "2" "wAA=" "8CA=" # WPA2-Enterprise patch which replaces a double-free with nop, see #8082
79+
patchFile "eap.o" "26352" "9" "dlBvcnRGcmVl" "ejJFYXBGcmVl" # special vPortFree to recover leaked memory
2780
elif [[ ${VERSION} == "NONOSDK22x"* ]]; then
2881
addSymbol_system_func1 "0x54"
82+
patchFile "eap.o" "3059" "2" "wAA=" "8CA=" # WPA2-Enterprise patch which replaces a double-free with nop, see #8082
83+
patchFile "eap.o" "26356" "9" "dlBvcnRGcmVl" "ejJFYXBGcmVl" # special vPortFree to recover leaked memory
2984
elif [[ ${VERSION} == "NONOSDK3"* ]]; then
3085
addSymbol_system_func1 "0x60"
86+
patchFile "eap.o" "3059" "2" "wAA=" "8CA=" # WPA2-Enterprise patch which replaces a double-free with nop, see #8082
87+
patchFile "eap.o" "26356" "9" "dlBvcnRGcmVl" "ejJFYXBGcmVl" # special vPortFree to recover leaked memory
3188
else
3289
echo "WARN: Unknown address for system_func1() called by system_restart_local()"
3390
fi
3491

35-
xtensa-lx106-elf-ar r libmain.a eagle_lwip_if.o user_interface.o
36-
rm -f eagle_lwip_if.o user_interface.o
92+
if [[ $(sha256sum eap.o | awk '{print $1}') != $eapcs ]]; then
93+
xtensa-lx106-elf-ar r libwpa2.a eap.o
94+
fi
95+
if [[ $(sha256sum user_interface.o | awk '{print $1}') != $uics || $(sha256sum eagle_lwip_if.o | awk '{print $1}') != $lwipcs ]]; then
96+
xtensa-lx106-elf-ar r libmain.a eagle_lwip_if.o user_interface.o
97+
fi
98+
rm -f eagle_lwip_if.o user_interface.o eap.o

0 commit comments

Comments
 (0)