Skip to content

Commit 17f1fb7

Browse files
authored
Merge pull request #293 from mullender/fix/auth-precompute-stack-overflow
Fix/auth precompute stack overflow
2 parents fa0b99e + 67b65f4 commit 17f1fb7

3 files changed

Lines changed: 72 additions & 1 deletion

File tree

main/NfcManager.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ void NfcManager::initAuthPrecompute() {
6868
xQueueSend(m_authCtxFreeQueue, &item, 0);
6969
}
7070

71-
BaseType_t ok = xTaskCreateUniversal(authPrecomputeTaskEntry, "hk_auth_precompute", 4096, this, 3, &m_authPrecomputeTaskHandle, 0);
71+
// 8192, not 4096: constructing a DDKAuthenticationContext runs mbedTLS P-256
72+
// key generation. Measured usage is 4056-4288 bytes and varies with the
73+
// random key material, so a 4096-byte stack left as little as 40 bytes of
74+
// headroom and intermittently overflowed -- panicking mid-transaction, which
75+
// the phone reported as a protocol error.
76+
BaseType_t ok = xTaskCreateUniversal(authPrecomputeTaskEntry, "hk_auth_precompute", kAuthPrecomputeStackBytes, this, 3, &m_authPrecomputeTaskHandle, 0);
7277
if (ok != pdPASS || !m_authPrecomputeTaskHandle) {
7378
ESP_LOGE(TAG, "Failed to start auth precompute task.");
7479
m_authPrecomputeTaskHandle = nullptr;
@@ -160,6 +165,23 @@ void NfcManager::authPrecomputeTask() {
160165
const auto durationMs =
161166
std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count();
162167

168+
169+
// Constructing the context runs mbedTLS P-256 key generation. Report the
170+
// headroom straight after: an overflow here reboots the device mid-
171+
// transaction, which is indistinguishable from a hang in the logs unless
172+
// the reset reason is checked at boot.
173+
{
174+
const UBaseType_t freeWords = uxTaskGetStackHighWaterMark(nullptr);
175+
const unsigned freeBytes = (unsigned)(freeWords * sizeof(StackType_t));
176+
if (freeBytes < 768) {
177+
ESP_LOGE(TAG, "precompute task stack CRITICALLY LOW: %u bytes free of %u",
178+
freeBytes, (unsigned)kAuthPrecomputeStackBytes);
179+
} else {
180+
ESP_LOGD(TAG, "precompute task stack headroom: %u bytes free of %u", freeBytes,
181+
(unsigned)kAuthPrecomputeStackBytes);
182+
}
183+
}
184+
163185
if (!item->ctx) {
164186
ESP_LOGE(TAG, "Auth precompute: allocation failed.");
165187
xQueueSend(m_authCtxFreeQueue, &item, 0);
@@ -445,6 +467,21 @@ void NfcManager::handleTagPresence(const std::vector<uint8_t>& uid, const std::a
445467

446468
auto stopTime = std::chrono::high_resolution_clock::now();
447469
ESP_LOGI(TAG, "Total processing time: %lli ms", std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count());
470+
// Headroom check. This task runs mbedTLS P-256 operations (ECDH, ECDSA) on
471+
// top of the reader's frame buffers, and an overflow here would look
472+
// exactly like the observed symptom: a reboot part-way through a
473+
// transaction. Reported every time so a downward trend is visible.
474+
const UBaseType_t stackFreeWords = uxTaskGetStackHighWaterMark(nullptr);
475+
if (stackFreeWords < 512) {
476+
ESP_LOGW(TAG, "nfc task stack headroom LOW: %u bytes free",
477+
(unsigned)(stackFreeWords * sizeof(StackType_t)));
478+
} else {
479+
ESP_LOGD(TAG, "nfc task stack headroom: %u bytes free",
480+
(unsigned)(stackFreeWords * sizeof(StackType_t)));
481+
}
482+
ESP_LOGD(TAG, "heap: %u free, %u min-ever", (unsigned)esp_get_free_heap_size(),
483+
(unsigned)esp_get_minimum_free_heap_size());
484+
448485
}
449486

450487
/**

main/include/NfcManager.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ class NfcManager {
9595

9696
static const char* TAG;
9797
AppEventLoop::SubscriptionHandle m_hk_event;
98+
// Stack for the hk_auth_precompute task. mbedTLS P-256 key generation was
99+
// measured using 4056-4288 bytes, so 4096 was not survivable.
100+
static constexpr uint32_t kAuthPrecomputeStackBytes = 8192;
101+
98102
enum PinFunctions {
99103
SCK,
100104
MISO,

main/main.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,36 @@ void setup() {
112112
if (err != ESP_OK) {
113113
ESP_LOGE("Main", "Failed to create default event loop: %d", err);
114114
}
115+
// Why did we just boot? Without this a crash-reboot is indistinguishable in
116+
// the logs from a hang: the log simply stops and later resumes. The reset
117+
// reason separates a software panic from a watchdog timeout from a brownout,
118+
// which need entirely different fixes.
119+
{
120+
const esp_reset_reason_t why = esp_reset_reason();
121+
const char *name = "unknown";
122+
switch (why) {
123+
case ESP_RST_POWERON: name = "power-on"; break;
124+
case ESP_RST_EXT: name = "external pin"; break;
125+
case ESP_RST_SW: name = "software restart"; break;
126+
case ESP_RST_PANIC: name = "PANIC (exception / assert)"; break;
127+
case ESP_RST_INT_WDT: name = "INTERRUPT WATCHDOG"; break;
128+
case ESP_RST_TASK_WDT: name = "TASK WATCHDOG"; break;
129+
case ESP_RST_WDT: name = "other watchdog"; break;
130+
case ESP_RST_DEEPSLEEP:name = "deep sleep wake"; break;
131+
case ESP_RST_BROWNOUT: name = "BROWNOUT (supply dipped)"; break;
132+
case ESP_RST_SDIO: name = "SDIO"; break;
133+
default: break;
134+
}
135+
const bool unexpected = (why == ESP_RST_PANIC || why == ESP_RST_INT_WDT ||
136+
why == ESP_RST_TASK_WDT || why == ESP_RST_WDT ||
137+
why == ESP_RST_BROWNOUT);
138+
if (unexpected) {
139+
ESP_LOGE("Boot", "*** UNEXPECTED RESET: %s (reason %d) ***", name, (int)why);
140+
} else {
141+
ESP_LOGI("Boot", "Reset reason: %s (%d)", name, (int)why);
142+
}
143+
}
144+
115145
readerDataManager = std::make_unique<ReaderDataManager>();
116146
configManager = std::make_unique<ConfigManager>();
117147
configManager->begin();

0 commit comments

Comments
 (0)