|
1 | 1 | /* |
2 | 2 | Cooperative multitasking library for Arduino |
3 | | -Copyright (c) 2015-2025 Anatoli Arkhipenko |
| 3 | +Copyright (c) 2015-2026 Anatoli Arkhipenko |
4 | 4 |
|
5 | 5 | Changelog: |
6 | 6 | v1.0.0: |
@@ -307,6 +307,13 @@ v4.0.3: |
307 | 307 | v4.0.4: |
308 | 308 | 2025-11-15: |
309 | 309 | - bug: set callbacks should not be available for _TASK_OO_CALLBACKS mode |
| 310 | +
|
| 311 | +v4.0.5: |
| 312 | + 2026-04-16: |
| 313 | + - bug fix: use-after-free in Scheduler::execute() when a callback destroys |
| 314 | + the next task in the chain (e.g. painlessMesh BufferedConnection teardown). |
| 315 | + Promoted nextTask to scheduler member iNextExecute so deleteTask() can |
| 316 | + advance it before the memory is freed. |
310 | 317 | */ |
311 | 318 |
|
312 | 319 | #include "TaskSchedulerDeclarations.h" |
@@ -1023,6 +1030,7 @@ void Scheduler::init() { |
1023 | 1030 | iFirst = NULL; |
1024 | 1031 | iLast = NULL; |
1025 | 1032 | iCurrent = NULL; |
| 1033 | + iNextExecute = NULL; |
1026 | 1034 |
|
1027 | 1035 | iPaused = false; |
1028 | 1036 |
|
@@ -1076,11 +1084,16 @@ void Scheduler::init() { |
1076 | 1084 | */ |
1077 | 1085 | void Scheduler::deleteTask(Task& aTask) { |
1078 | 1086 | // Can only delete own tasks |
1079 | | - if (aTask.iScheduler != this) |
| 1087 | + if (aTask.iScheduler != this) |
1080 | 1088 | return; |
1081 | | - |
| 1089 | + |
1082 | 1090 | iEnabled = false; |
1083 | 1091 |
|
| 1092 | + // If execute() is mid-iteration and this task is next in line, |
| 1093 | + // advance the pointer before we unlink and the memory is freed. |
| 1094 | + if (iNextExecute == &aTask) |
| 1095 | + iNextExecute = aTask.iNext; |
| 1096 | + |
1084 | 1097 | aTask.iScheduler = NULL; |
1085 | 1098 | if (aTask.iPrev == NULL) { |
1086 | 1099 | if (aTask.iNext == NULL) { |
@@ -1549,7 +1562,6 @@ bool Scheduler::execute() { |
1549 | 1562 | #endif // _TASK_SLEEP_ON_IDLE_RUN |
1550 | 1563 |
|
1551 | 1564 |
|
1552 | | - Task *nextTask; // support for deleting the task in the onDisable method |
1553 | 1565 | iCurrent = iFirst; |
1554 | 1566 |
|
1555 | 1567 | iActiveTasks = 0; |
@@ -1605,7 +1617,7 @@ bool Scheduler::execute() { |
1605 | 1617 | if (iHighPriority) idleRun = iHighPriority->execute() && idleRun; |
1606 | 1618 | iCurrentScheduler = this; |
1607 | 1619 | #endif // _TASK_PRIORITY |
1608 | | - nextTask = iCurrent->iNext; |
| 1620 | + iNextExecute = iCurrent->iNext; |
1609 | 1621 | do { |
1610 | 1622 | if ( iCurrent->iStatus.enabled ) { |
1611 | 1623 | iActiveTasks++; |
@@ -1763,7 +1775,7 @@ bool Scheduler::execute() { |
1763 | 1775 | #endif // #ifdef _TASK_SELF_DESTRUCT |
1764 | 1776 | } while (0); //guaranteed single run - allows use of "break" to exit |
1765 | 1777 |
|
1766 | | - iCurrent = nextTask; |
| 1778 | + iCurrent = iNextExecute; |
1767 | 1779 |
|
1768 | 1780 | #ifdef _TASK_TIMECRITICAL |
1769 | 1781 | iCPUCycle += ( (_task_micros() - tPassStart) - (tTaskFinish - tTaskStart) ); |
|
0 commit comments