CUPS Local Privilege Escalation and Sandbox Escapes

Gotham Digital Science has discovered multiple vulnerabilities in Apple’s CUPS print system affecting macOS 10.13.4 and earlier and multiple Linux distributions.  All information in this post has been shared with Apple and other affected vendors prior to publication as part of the coordinated disclosure process.  All code is excerpted from Apple’s open source CUPS repository located at

The vulnerabilities allow for local privilege escalation to root (CVE-2018-4180), multiple sandbox escapes (CVE-2018-4182 and CVE-2018-4183), and unsandboxed root-level local file reads (CVE-2018-4181).  A related AppArmor-specific sandbox escape (CVE-2018-6553) was also discovered affecting Linux distributions such as Debian and Ubuntu.  When chained together, these vulnerabilities allow an unprivileged local attacker to escalate to unsandboxed root privileges on affected systems.

Affected Linux systems include those that allow non-root users to modify cupsd.conf such as Debian and Ubuntu.  Redhat and related distributions are generally not vulnerable by default.  Consult distribution-specific documentation and security advisories for more information.

The vulnerabilities were patched in macOS 10.13.5, and patches are currently available for Debian and Ubuntu systems.  GDS would like to thank Apple, Debian, and Canonical for working to patch the vulnerabilities, and CERT for assisting in vendor coordination.

CVE-2018-4180 – Dan Bastone
CVE-2018-4182 – Dan Bastone
CVE-2018-4183 – Dan Bastone and Eric Rafaloff
CVE-2018-6553 – Dan Bastone
CVE-2018-4181 – Eric Rafaloff and John Dunlap

02/21/2018 – Initial disclosure to Apple
02/26/2018 – Initial disclosure to Debian and Canonical (CVE-2018-6553)
03/02/2018 – Issues confirmed by Apple
05/04/2018 – Apple requests delay of public disclosure due to downstream vendor coordination with CERT
06/01/2018 – Apple releases fixes in macOS 10.13.5
06/05/2018 – Apple publishes patches on their public Github repository
07/11/2018 – Public disclosure

Apple Security Advisory (updated 7/11/18):

Linux Vendor Advisories:


This post describes the privilege escalation and sandbox escape vulnerabilities and their fixes.  Exploit code is currently being withheld, and will be released at a later date.  Details of the root-level local file read issue (CVE-2018-4181) will be released in a follow-up blog post.

Local Privilege Escalation to Root Due to Insecure Environment Variable Handling – CVE-2018-4180

Affected versions of CUPS allow for the SetEnv and PassEnv directives to be specified in the cupsd.conf file, which is editable by non-root users using the cupsctl binary.  This allows attacker-controlled environment variables to be passed to CUPS backends, some of which are run as root.  By passing malicious values in environment variables to affected backends, it is possible to execute an attacker-supplied binary as root, subject to sandbox restrictions.


Multiple vulnerable code paths exist for this issue, one of which is shown below.  The environment variable is used to construct a filename on lines 804 and 807 that is executed on line 819.

cups/backend/dnssd.c:800  /*801   * Get the filename of the backend...802   */803804   if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)805     cups_serverbin = CUPS_SERVERBIN;806807   snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);[…]817   fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename);818819   execv(filename, argv);

The issue was remediated by moving the SetEnv and PassEnv configuration directives from cupsd.conf to cups-files.conf, which is only editable by root.  Additionally, sensitive environment variables that may have security implications have been restricted and can no longer be set using these directives.  This effectively prevents all known exploit vectors.

macOS cups-exec Sandbox Bypass Due to Insecure Error Handling – CVE-2018-4182

It is possible to cause cups-exec to execute backends without a sandbox profile by causing cupsdCreateProfile() to fail.  An attacker that has obtained sandboxed root access can accomplish this by setting the CUPS temporary directory to immutable using chflags, which will prevent the profile from being written to disk.

Chaining this vulnerability with CVE-2018-4180 results in unsandboxed root code execution.

When /var/spool/cups/tmp is set to immutable, the following sequence will fail, resulting in DefaultProfile being set to NULL in cupsdStartServer().  This error is ignored, and execution continues.

DefaultProfile =          NULL   cupsdCreateProfile()    ^      cupsTempFile2()      |         cupsTempFd()      |            open("/var/spool/cups/tmp/...") = -1 [Operation not permitted]

When cupsdStartProcess() is later called to execute a backend, the NULL default profile is passed as an argument:

scheduler/client.c:3819   if (cupsdStartProcess(command, argv, envp, infile, fds[1], CGIPipes[1],3820          -1, -1, root, DefaultProfile, NULL, &pid) < 0)

The process is then executed unsandboxed, because the profile is NULL.

scheduler/process.c:455 int               /* O - Process ID or 0 */456 cupsdStartProcess(457     const char  *command,     /* I - Full path to command */458     char        *argv[],      /* I - Command-line arguments */459     char        *envp[],      /* I - Environment */460     int         infd,         /* I - Standard input file descriptor */461     int         outfd,        /* I - Standard output file descriptor */462     int         errfd,        /* I - Standard error file descriptor */463     int         backfd,       /* I - Backchannel file descriptor */464     int         sidefd,       /* I - Sidechannel file descriptor */465     int         root,         /* I - Run as root? */466     void        *profile,     /* I - Security profile to use */467     cupsd_job_t *job,         /* I - Job associated with process */468     int         *pid)         /* O - Process ID */469 {[...]545  /*546   * Use helper program when we have a sandbox profile...547   */548549 #if !USE_POSIX_SPAWN550   if (profile)551 #endif /* !USE_POSIX_SPAWN */552   {553     snprintf(cups_exec, sizeof(cups_exec), "%s/daemon/cups-exec", ServerBin);554     snprintf(user_str, sizeof(user_str), "%d", user);555     snprintf(group_str, sizeof(group_str), "%d", Group);556     snprintf(nice_str, sizeof(nice_str), "%d", FilterNice);557558     real_argv[0] = cups_exec;559     real_argv[1] = (char *)"-g";560     real_argv[2] = group_str;561     real_argv[3] = (char *)"-n";562     real_argv[4] = nice_str;563     real_argv[5] = (char *)"-u";564     real_argv[6] = user_str;565     real_argv[7] = profile ? profile : "none";566     real_argv[8] = (char *)command;

The following debug output shows execution of exploits for CVE-2018-4180 and CVE-2018-4182.

CUPS_SERVERBIN is set to the attacker-controlled directory by the exploit:

d [18/Feb/2018:21:50:21 -0500] cupsdSetEnv: CUPS_SERVERBIN=/tmp/exploit   [...]D [18/Feb/2018:21:50:21 -0500] [Job 69] envp[1]="CUPS_SERVERBIN=/tmp/exploit"

dnssd is executed as root with a valid sandbox profile:

cupsdStartProcess(command="/usr/libexec/cups/backend/dnssd", argv=0x7ffee67eac30, envp=0x7ffee67ece90, infd=-1, outfd=-1, errfd=15, backfd=17, sidefd=19, root=1, profile=0x7f9339d1acb0, job=0x7f9339e203d0(69), pid=0x7f9339e20538) = 2463

dnssd then executes its sub-backend from the attacker-controlled CUPS_SERVERBIN containing the exploit payload. On this initial execution, the write to /exploit.txt will fail, and the payload will set the CUPS temp directory to immutable.

D [18/Feb/2018:21:50:21 -0500] [Job 69] Executing backend \"/tmp/exploit/backend/dnssd\"...D [18/Feb/2018:21:50:21 -0500] [Job 69] /tmp/exploit/backend/dnssd: line 2: /exploit.txt: Operation not permitted

The payload then triggers the exploit again:

D [18/Feb/2018:21:50:21 -0500] [Job 70] envp[1]="CUPS_SERVERBIN=/tmp/exploit"

This time, the sandbox profile is prevented from being written:

d [18/Feb/2018:21:50:21 -0500] cupsdCreateProfile(job_id=70, allow_networking=0) = NULLE [18/Feb/2018:21:50:21 -0500] Unable to create security profile: Operation not permittedd [18/Feb/2018:21:50:21 -0500] cupsdCreateProfile(job_id=70, allow_networking=1) = NULLE [18/Feb/2018:21:50:21 -0500] Unable to create security profile: Operation not permitted

This causes cupsdStartProcess to be called with a NULL profile argument, executing dnssd as root outside the sandbox:

d [18/Feb/2018:21:50:21 -0500] cupsdStartProcess(command="/usr/libexec/cups/backend/dnssd", argv=0x7ffee67fa7a0, envp=0x7ffee67fca00, infd=-1, outfd=-1, errfd=14, backfd=16, sidefd=18, root=1, profile=0x0, job=0x7f9339f12480(70), pid=0x7f9339f125e8) = 2469

Finally, the sub-backend executes outside the sandbox, writes to /exploit.txt, and exits successfully:

D [18/Feb/2018:21:50:21 -0500] [Job 70] Executing backend \"/tmp/exploit/backend/dnssd\"...D [18/Feb/2018:21:50:21 -0500] [Job 70] PID 2469 (/usr/libexec/cups/backend/dnssd) exited with no errors.

The issue was remediated through the addition of error-handling code and sanity checks that prevent backends from executing outside of a sandbox profile.

macOS cups-exec Sandbox Bypass Due to Profile Misconfiguration – CVE-2018-4183

The sandbox profile dynamically generated by cupsdCreateProfile() unintentionally allows write access to /etc/cups.  This can be used by an attacker that has obtained sandboxed root access to alter /etc/cups/cups-files.conf, leading to unsandboxed root code execution.

The issue is caused by the fact that both ServerRoot and StateDir are set to /etc/cups.  The sandbox profile first denies write access to ServerRoot, but subsequently allows write access to StateDir.  This is shown in cupsdCreateProfile() below.

cups/scheduler/process.c:142   cupsFilePrintf(fp,143                  "(deny file-write*\n"144                  "  (regex"145        " #\"^%s$\""     /* ServerRoot */146        " #\"^%s/\""     /* ServerRoot/... */147        " #\"^/private/etc$\""148        " #\"^/private/etc/\""[...]194   cupsFilePrintf(fp,195                  "(allow file-write* file-read-data file-read-metadata\n"196                  "  (regex"197        " #\"^%s$\""     /* TempDir */198        " #\"^%s/\""     /* TempDir/... */199        " #\"^%s$\""     /* CacheDir */200        " #\"^%s/\""     /* CacheDir/... */201        " #\"^%s$\""     /* StateDir */202        " #\"^%s/\""     /* StateDir/... */203        "))\n",204        temp, temp, cache, cache, state, state);

This results in the following conflicting sandbox profile directives, ultimately allowing write access to /etc/cups:

(deny file-write*  (regex #"^/private/etc/cups$" #"^/private/etc/cups/" #"^/private/etc$" #"^/private/etc/" #"^/usr/local/etc$" #"^/usr/local/etc/" #"^/Library$" #"^/Library/" #"^/System$" #"^/System/"))(allow file-write* file-read-data file-read-metadata  (regex #"^/private/var/spool/cups/tmp$" #"^/private/var/spool/cups/tmp/" #"^/private/var/spool/cups/cache$" #"^/private/var/spool/cups/cache/" #"^/private/etc/cups$" #"^/private/etc/cups/"))

The sandbox profile was corrected to disallow writes to /etc/cups by removing the StateDir entries.

AppArmor cupsd Sandbox Bypass Due to Use of Hard Links – CVE-2018-6553

It is possible to bypass the AppArmor cupsd sandbox by invoking the dnssd backend using an alternate name that has been hard linked to dnssd.  Both Debian and Ubuntu use AppArmor and shipped the mdns backend in this manner, in contrast to macOS and other systems that use symbolic links.  Invoking the mdns backend causes the AppArmor profile to treat the backend as 3rd party, removing sandbox restrictions.

The cups backend directory and an excerpt of the cups .deb post-installation script shows the use of hard links.

 ls -l /usr/lib/cups/backend/{dnssd,mdns}-rwxr--r-- 3 root root 18424 Aug 22 17:26 /usr/lib/cups/backend/dnssd-rwxr--r-- 3 root root 18424 Aug 22 17:26 /usr/lib/cups/backend/mdns$ stat /usr/lib/cups/backend/{dnssd,mdns}  File: '/usr/lib/cups/backend/dnssd'  Size: 18424        Blocks: 40         IO Block: 4096   regular fileDevice: 801h/2049d   Inode: 12615       Links: 3Access: (0744/-rwxr--r--)  Uid: (    0/    root)   Gid: (    0/    root)Access: 2018-02-18 13:08:11.761022386 -0800Modify: 2017-08-22 17:26:53.000000000 -0700Change: 2018-02-14 11:32:56.356309575 -0800 Birth: -  File: '/usr/lib/cups/backend/mdns'  Size: 18424        Blocks: 40         IO Block: 4096   regular fileDevice: 801h/2049d   Inode: 12615       Links: 3Access: (0744/-rwxr--r--)  Uid: (    0/    root)   Gid: (    0/    root)Access: 2018-02-18 13:08:11.761022386 -0800Modify: 2017-08-22 17:26:53.000000000 -0700Change: 2018-02-14 11:32:56.356309575 -0800 Birth: -DEBIAN/postinst: (from cups_2.2.1-8_amd64.deb)71       if [ "$module" = "dnssd" ]; then72         ln /usr/lib/cups/backend/dnssd /usr/lib/cups/backend/mdns73       fi

The AppArmor sandbox profile explicitly whitelists known backends, but neglects to include mdns. Because mdns is hard linked to dnssd, it matches the rule on line 95. If symbolic links were used instead, line 81 would match and the backend would be sandboxed as intended.

/etc/apparmor.d/usr.sbin.cupsd:79   # backends which come with CUPS can be confined80   /usr/lib/cups/backend/bluetooth ixr,81   /usr/lib/cups/backend/dnssd ixr,82   /usr/lib/cups/backend/http ixr,83   /usr/lib/cups/backend/ipp ixr,84   /usr/lib/cups/backend/lpd ixr,85   /usr/lib/cups/backend/parallel ixr,86   /usr/lib/cups/backend/serial ixr,87   /usr/lib/cups/backend/snmp ixr,88   /usr/lib/cups/backend/socket ixr,89   /usr/lib/cups/backend/usb ixr,90   # we treat cups-pdf specially, since it needs to write into /home91   # and thus needs extra paranoia92   /usr/lib/cups/backend/cups-pdf Px,93   # third party backends get no restrictions as they often need high94   # privileges and this is beyond our control95   /usr/lib/cups/backend/* Cx -> third_party,

The AppArmor profile was updated to explicitly sandbox the mdns backend.

*** This is a Security Bloggers Network syndicated blog from Blog authored by Dan Bastone. Read the original post at: