Bash Privileged-Mode Vulnerabilities in Parallels Desktop and CDPATH Handling in MacOS

We discussed this vulnerability during Episode 207 on 01 May 2023

CVE-2023-27322 - Local Privilege Escalation Through Parallels Service

First a little bit of background about bash and privileged mode. Normally the Bash shell will drop privileges when it is started under a setuid/setgid situation. It will set the effective user/group identifiers to that of the real identifiers. Privileged mode will retain the (presumably) more privileged effective identifiers, but lock itself down a little to limit the degree of control the original user has over Bash’s execution. It won’t process the $ENV or $BASH_ENV files, shell functions won’t be inherited from the environments.

The interesting thing here is how privileges change across child processes. We start with the original Parallels Service, which is a setuid and setgid binary. It will execute an embeded script within a non-interactive Bash shell. To do so it makes a call to setuid(geteuid()) setting the real uid to the effective uid (root), then it calls fork() and execv to spawn a bash shell. Importantly here execv wraps execvepassing in the current (attacker controlled) environment to the newly spawned process. This newly spawned process, despite the earlier setuid binary will be detected as a setgid execution, prompted Bash to automatically drop its privileges and ignore processing the environment variables. At this stage bash is properly dropping its privileged and not allowing the original user to influence execution.

However, the script continues, it spawns its own child process a watchdog script. Whats interesting this time is that because Bash “dropped” the privileges, when this nested script executes, its process will be called with the effective and real user/group identifiers matching, so Bash won’t know to restrict itself. This nested call, will trust the environment values and so an attacker could provide malicious shell functions that would be inherited not by the first embedded script but by the further nested watchdog script.

The post also covers a couple more bugs, one with prl_update-helper invoking the inittool script, this one is a bit more direct. It is (presumably) not a setgid binary, and before invoking the script it uses a setuid(geteuid()) call, so it runs as root and does not restrict itself. Similarly inittool2 executable is invoked from the inittool script. inittool2 will fork a child process to execute an embedded script which can be abused.