Skip to content

Commit adadc99

Browse files
committed
v4.0.5: bug fix: use-after-free in Scheduler::execute() when a callback destroys
1 parent b0d9d47 commit adadc99

8 files changed

Lines changed: 314 additions & 13 deletions

File tree

.github/workflows/test.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,3 +521,86 @@ jobs:
521521
run: |
522522
echo "=== Running Thread-Safe Tests ==="
523523
./test_thread_safe
524+
525+
test-uaf-regression:
526+
name: UAF Regression Tests (iNextExecute fix)
527+
runs-on: ubuntu-latest
528+
529+
steps:
530+
- name: Checkout
531+
uses: actions/checkout@v4
532+
533+
- name: Install dependencies
534+
run: |
535+
sudo apt-get update
536+
sudo apt-get install -y cmake build-essential libgtest-dev pkg-config
537+
538+
- name: Build and install Google Test
539+
run: |
540+
cd /usr/src/gtest
541+
sudo cmake .
542+
sudo cmake --build . --target all
543+
sudo cp lib/*.a /usr/lib || sudo cp *.a /usr/lib
544+
545+
- name: Create CMakeLists.txt for UAF Regression Tests
546+
run: |
547+
cat > CMakeLists.txt << 'EOF'
548+
cmake_minimum_required(VERSION 3.10)
549+
project(TaskSchedulerUafRegressionTests VERSION 1.0.0)
550+
551+
set(CMAKE_CXX_STANDARD 14)
552+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
553+
554+
# Find required packages
555+
find_package(PkgConfig REQUIRED)
556+
find_package(Threads REQUIRED)
557+
558+
# Include directories
559+
include_directories(${CMAKE_SOURCE_DIR}/src)
560+
include_directories(${CMAKE_SOURCE_DIR}/tests)
561+
562+
# Gather source files
563+
file(GLOB TASKSCHEDULER_SOURCES
564+
"${CMAKE_SOURCE_DIR}/src/*.cpp"
565+
"${CMAKE_SOURCE_DIR}/src/*.c"
566+
)
567+
568+
# Create the UAF regression test executable
569+
add_executable(test_uaf_regression
570+
tests/test-scheduler-uaf-regression.cpp
571+
${TASKSCHEDULER_SOURCES}
572+
)
573+
574+
# Link libraries
575+
target_link_libraries(test_uaf_regression
576+
gtest
577+
gtest_main
578+
pthread
579+
)
580+
581+
# Compiler definitions for Arduino compatibility
582+
target_compile_definitions(test_uaf_regression PRIVATE
583+
ARDUINO=300
584+
)
585+
586+
# Compiler flags
587+
target_compile_options(test_uaf_regression PRIVATE
588+
-Wall
589+
-Wextra
590+
-O2
591+
)
592+
593+
# Enable testing
594+
enable_testing()
595+
add_test(NAME UafRegressionTests COMMAND test_uaf_regression)
596+
EOF
597+
598+
- name: Build UAF regression tests
599+
run: |
600+
cmake .
601+
make -j$(nproc)
602+
603+
- name: Run UAF regression tests
604+
run: |
605+
echo "=== Running UAF Regression Tests ==="
606+
./test_uaf_regression

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Task Scheduler
22
### Cooperative multitasking for Arduino, ESPx, STM32 and other microcontrollers
3-
#### Version 4.0.4: 2026-01-16 [Latest updates](https://github.com/arkhipenko/TaskScheduler/wiki/Latest-Updates)
3+
#### Version 4.0.5: 2026-04-16 [Latest updates](https://github.com/arkhipenko/TaskScheduler/wiki/Latest-Updates)
44

55
[![arduino-library-badge](https://www.ardu-badge.com/badge/TaskScheduler.svg?)](https://www.ardu-badge.com/TaskScheduler)
66

TaskScheduler.code-workspace

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"folders": [
3+
{
4+
"path": "."
5+
}
6+
],
7+
"settings": {}
8+
}

library.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"maintainer": true
1717
}
1818
],
19-
"version": "4.0.4",
19+
"version": "4.0.5",
2020
"frameworks": "arduino",
2121
"platforms": "*"
2222
}

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=TaskScheduler
2-
version=4.0.4
2+
version=4.0.5
33
author=Anatoli Arkhipenko <arkhipenko@hotmail.com>
44
maintainer=Anatoli Arkhipenko <arkhipenko@hotmail.com>
55
sentence=Cooperative multitasking for Arduino, ESPx, STM32 and other microcontrollers.

src/TaskScheduler.h

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
Cooperative multitasking library for Arduino
3-
Copyright (c) 2015-2025 Anatoli Arkhipenko
3+
Copyright (c) 2015-2026 Anatoli Arkhipenko
44
55
Changelog:
66
v1.0.0:
@@ -307,6 +307,13 @@ v4.0.3:
307307
v4.0.4:
308308
2025-11-15:
309309
- 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.
310317
*/
311318

312319
#include "TaskSchedulerDeclarations.h"
@@ -1023,6 +1030,7 @@ void Scheduler::init() {
10231030
iFirst = NULL;
10241031
iLast = NULL;
10251032
iCurrent = NULL;
1033+
iNextExecute = NULL;
10261034

10271035
iPaused = false;
10281036

@@ -1076,11 +1084,16 @@ void Scheduler::init() {
10761084
*/
10771085
void Scheduler::deleteTask(Task& aTask) {
10781086
// Can only delete own tasks
1079-
if (aTask.iScheduler != this)
1087+
if (aTask.iScheduler != this)
10801088
return;
1081-
1089+
10821090
iEnabled = false;
10831091

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+
10841097
aTask.iScheduler = NULL;
10851098
if (aTask.iPrev == NULL) {
10861099
if (aTask.iNext == NULL) {
@@ -1549,7 +1562,6 @@ bool Scheduler::execute() {
15491562
#endif // _TASK_SLEEP_ON_IDLE_RUN
15501563

15511564

1552-
Task *nextTask; // support for deleting the task in the onDisable method
15531565
iCurrent = iFirst;
15541566

15551567
iActiveTasks = 0;
@@ -1605,7 +1617,7 @@ bool Scheduler::execute() {
16051617
if (iHighPriority) idleRun = iHighPriority->execute() && idleRun;
16061618
iCurrentScheduler = this;
16071619
#endif // _TASK_PRIORITY
1608-
nextTask = iCurrent->iNext;
1620+
iNextExecute = iCurrent->iNext;
16091621
do {
16101622
if ( iCurrent->iStatus.enabled ) {
16111623
iActiveTasks++;
@@ -1763,7 +1775,7 @@ bool Scheduler::execute() {
17631775
#endif // #ifdef _TASK_SELF_DESTRUCT
17641776
} while (0); //guaranteed single run - allows use of "break" to exit
17651777

1766-
iCurrent = nextTask;
1778+
iCurrent = iNextExecute;
17671779

17681780
#ifdef _TASK_TIMECRITICAL
17691781
iCPUCycle += ( (_task_micros() - tPassStart) - (tTaskFinish - tTaskStart) );

src/TaskSchedulerDeclarations.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
* @file TaskSchedulerDeclarations.h
33
* @brief Cooperative multitasking library for Arduino microcontrollers
44
* @author Anatoli Arkhipenko
5-
* @version 4.0.2
6-
* @date 2015-2025
7-
* @copyright Copyright (c) 2015-2025 Anatoli Arkhipenko
5+
* @version 4.0.5
6+
* @date 2015-2026
7+
* @copyright Copyright (c) 2015-2026 Anatoli Arkhipenko
88
*
99
* @details A lightweight implementation of cooperative multitasking (task scheduling) supporting:
1010
* - Periodic task execution, with dynamic execution period in milliseconds (default) or microseconds
@@ -3066,7 +3066,7 @@ class Scheduler {
30663066
#ifdef _TASK_THREAD_SAFE
30673067
__TASK_INLINE void processRequests();
30683068
#endif
3069-
Task *iFirst, *iLast, *iCurrent; // pointers to first, last and current tasks in the chain
3069+
Task *iFirst, *iLast, *iCurrent, *iNextExecute; // pointers to first, last, current and next-to-execute tasks in the chain
30703070

30713071
volatile bool iPaused, iEnabled;
30723072
unsigned long iActiveTasks;

0 commit comments

Comments
 (0)