Activity log for bug #1862933

Date Who What changed Old value New value Message
2020-02-12 12:22:14 Maximilien Bourgeteau bug added bug
2020-02-12 12:22:14 Maximilien Bourgeteau attachment added poc.c https://bugs.launchpad.net/bugs/1862933/+attachment/5327644/+files/poc.c
2020-02-12 12:22:34 Maximilien Bourgeteau bug task added apport
2020-02-12 12:23:00 Maximilien Bourgeteau bug added subscriber Ubuntu Security Team
2020-02-12 12:28:00 Maximilien Bourgeteau description Vulnerable code (from data/apport): 700 # we prefer having a file mode of 0 while writing; this doesn't work 701 # for suid binaries as we completely drop privs and thus can't chmod 702 # them later on 703 if pidstat.st_uid != os.getuid(): 704 mode = 0o640 705 else: 706 mode = 0 707 reportfile = os.fdopen(os.open(report, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode), 'w+b') 708 assert reportfile.fileno() > sys.stderr.fileno() 709 710 # Make sure the crash reporting daemon can read this report 711 try: 712 gid = pwd.getpwnam('whoopsie').pw_gid 713 os.chown(report, pidstat.st_uid, gid) 714 except (OSError, KeyError): 715 os.chown(report, pidstat.st_uid, pidstat.st_gid) 716 except (OSError, IOError) as e: 717 error_log('Could not create report file: %s' % str(e)) 718 sys.exit(1) The TOCTTOU takes place between the os.open and the os.chown call and can be fully achieved thanks to the Apport cron script (etc/cron.daily/apport): 1 #!/bin/sh -e 2 # clean all crash reports which are older than a week. 3 [ -d /var/crash ] || exit 0 4 find /var/crash/. ! -name . -prune -type f \( \( -size 0 -a \! -name '*.upload*' -a \! -name '*.drkonqi*' \) -o -mtime +7 \) -exec rm -f -- '{}' \; 5 find /var/crash/. ! -name . -prune -type d -regextype posix-extended -regex '.*/[0-9]{12}$' \( -mtime +7 \) -exec rm -Rf -- '{}' \; The interesting part in this daily script is that the crash reports gets removed if their size is 0. Since Apport drops real uid and gid, the crashed process owner can send signals during the report file creation. At this time, effective uid and gid are still root. We can also block Apport by replacing user settings file with a FIFO (~/.config/apport/settings). I'm using the FIFO way in my PoC but it can be done without it. To make Apport read user settings, the crashing program must not be located in one of those directories (taken from apport/fileutils.py): 78 pkg_whitelist = ['/bin/', '/boot', '/etc/', '/initrd', '/lib', '/sbin/', 79 '/opt', '/usr/', '/var'] # packages only ship executables in these directories Once Apport is blocked into FIFO reading, we send the SIGSTOP signal then we write "[main]\nunpackaged=1" into the FIFO so Apport won't exit after resuming (if unpackaged is 0 Apport directly exits because we're not in a "packaged" directory). After that we "single step" through Apport by sending SIGCONT and SIGSTOP consecutively in a loop until the report file is created with os.open. We must make sure os.chown hasn't been called and then we wait for the cron script to delete the report (it's created as root with mode 0 so only root can remove it). Once removed, we can replace it with a symbolic link/file of the same name so we can chown root owned files with the crashed process user and group. I think the impact of this vulnerability alone is low because fs.protected_symlinks prevents symlink resolution since we're in a sticky world writable directory (/var/crash), but if it's disabled, you can escalate privileges very easily. It still can be used in some kind of exploit chain though. My PoC does everything for you except symbolic link/file creation once the report gets deleted by the cron script, you have to create it manually then press enter to resume Apport and let the chown happen. You could also create a new crontab entry and directory then copy Apport cron script into it so you don't have to wait the entire day. Fix suggestions: - Use reportfile.fileno() instead of the report string for the os.chown call, and also add follow_symlinks=False argument just in case. - Remove the size 0 condition in the cron script (not sure about this one since the condition was there for a purpose). Vulnerable code (from data/apport):    700 # we prefer having a file mode of 0 while writing; this doesn't work    701 # for suid binaries as we completely drop privs and thus can't chmod    702 # them later on    703 if pidstat.st_uid != os.getuid():    704 mode = 0o640    705 else:    706 mode = 0    707 reportfile = os.fdopen(os.open(report, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode), 'w+b')    708 assert reportfile.fileno() > sys.stderr.fileno()    709    710 # Make sure the crash reporting daemon can read this report    711 try:    712 gid = pwd.getpwnam('whoopsie').pw_gid    713 os.chown(report, pidstat.st_uid, gid)    714 except (OSError, KeyError):    715 os.chown(report, pidstat.st_uid, pidstat.st_gid)    716 except (OSError, IOError) as e:    717 error_log('Could not create report file: %s' % str(e))    718 sys.exit(1) The TOCTTOU takes place between the os.open and the os.chown call and can be fully achieved thanks to the Apport cron script (etc/cron.daily/apport):      1 #!/bin/sh -e      2 # clean all crash reports which are older than a week.      3 [ -d /var/crash ] || exit 0      4 find /var/crash/. ! -name . -prune -type f \( \( -size 0 -a \! -name '*.upload*' -a \! -name '*.drkonqi*' \) -o -mtime +7 \) -exec rm -f -- '{}' \;      5 find /var/crash/. ! -name . -prune -type d -regextype posix-extended -regex '.*/[0-9]{12}$' \( -mtime +7 \) -exec rm -Rf -- '{}' \; The interesting part in this daily script is that the crash reports gets removed if their size is 0. Since Apport drops real uid and gid, the crashed process owner can send signals during the report file creation. At this time, effective uid and gid are still root. We can also block Apport by replacing user settings file with a FIFO (~/.config/apport/settings). I'm using the FIFO way in my PoC but it can be done without it. To make Apport read user settings, the crashing program must not be located in one of those directories (taken from apport/fileutils.py):     78 pkg_whitelist = ['/bin/', '/boot', '/etc/', '/initrd', '/lib', '/sbin/',     79 '/opt', '/usr/', '/var'] # packages only ship executables in these directories Once Apport is blocked into FIFO reading, we send the SIGSTOP signal then we write "[main]\nunpackaged=1" into the FIFO so Apport won't exit after resuming (if unpackaged is 0 Apport directly exits because we're not in a "packaged" directory). After that we "single step" through Apport by sending SIGCONT and SIGSTOP consecutively in a loop until the report file is created with os.open. We must make sure os.chown hasn't been called and then we wait for the cron script to remove the report (it's created as root with mode 0 so only root can remove it). Once removed, we can replace it with a symbolic link/file of the same name, resume Apport with SIGCONT then the file will now be owned by the crashed process user and group. I think the impact of this vulnerability alone is low because fs.protected_symlinks prevents symlink resolution since we're in a sticky world writable directory (/var/crash), but if it's disabled, you can escalate privileges very easily. It still can be used in some kind of exploit chain though. My PoC does everything for you except symbolic link/file creation once the report gets removed by the cron script, you have to create it manually then press enter to resume Apport and let the chown happen. You could also create a new crontab entry and directory then copy Apport cron script into it so you don't have to wait the entire day. Fix suggestions: - Use reportfile.fileno() instead of the report string for the os.chown call, and also add follow_symlinks=False argument just in case. - Remove the size 0 condition in the cron script (not sure about this one since the condition was there for a purpose).
2020-02-12 17:31:33 Maximilien Bourgeteau description Vulnerable code (from data/apport):    700 # we prefer having a file mode of 0 while writing; this doesn't work    701 # for suid binaries as we completely drop privs and thus can't chmod    702 # them later on    703 if pidstat.st_uid != os.getuid():    704 mode = 0o640    705 else:    706 mode = 0    707 reportfile = os.fdopen(os.open(report, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode), 'w+b')    708 assert reportfile.fileno() > sys.stderr.fileno()    709    710 # Make sure the crash reporting daemon can read this report    711 try:    712 gid = pwd.getpwnam('whoopsie').pw_gid    713 os.chown(report, pidstat.st_uid, gid)    714 except (OSError, KeyError):    715 os.chown(report, pidstat.st_uid, pidstat.st_gid)    716 except (OSError, IOError) as e:    717 error_log('Could not create report file: %s' % str(e))    718 sys.exit(1) The TOCTTOU takes place between the os.open and the os.chown call and can be fully achieved thanks to the Apport cron script (etc/cron.daily/apport):      1 #!/bin/sh -e      2 # clean all crash reports which are older than a week.      3 [ -d /var/crash ] || exit 0      4 find /var/crash/. ! -name . -prune -type f \( \( -size 0 -a \! -name '*.upload*' -a \! -name '*.drkonqi*' \) -o -mtime +7 \) -exec rm -f -- '{}' \;      5 find /var/crash/. ! -name . -prune -type d -regextype posix-extended -regex '.*/[0-9]{12}$' \( -mtime +7 \) -exec rm -Rf -- '{}' \; The interesting part in this daily script is that the crash reports gets removed if their size is 0. Since Apport drops real uid and gid, the crashed process owner can send signals during the report file creation. At this time, effective uid and gid are still root. We can also block Apport by replacing user settings file with a FIFO (~/.config/apport/settings). I'm using the FIFO way in my PoC but it can be done without it. To make Apport read user settings, the crashing program must not be located in one of those directories (taken from apport/fileutils.py):     78 pkg_whitelist = ['/bin/', '/boot', '/etc/', '/initrd', '/lib', '/sbin/',     79 '/opt', '/usr/', '/var'] # packages only ship executables in these directories Once Apport is blocked into FIFO reading, we send the SIGSTOP signal then we write "[main]\nunpackaged=1" into the FIFO so Apport won't exit after resuming (if unpackaged is 0 Apport directly exits because we're not in a "packaged" directory). After that we "single step" through Apport by sending SIGCONT and SIGSTOP consecutively in a loop until the report file is created with os.open. We must make sure os.chown hasn't been called and then we wait for the cron script to remove the report (it's created as root with mode 0 so only root can remove it). Once removed, we can replace it with a symbolic link/file of the same name, resume Apport with SIGCONT then the file will now be owned by the crashed process user and group. I think the impact of this vulnerability alone is low because fs.protected_symlinks prevents symlink resolution since we're in a sticky world writable directory (/var/crash), but if it's disabled, you can escalate privileges very easily. It still can be used in some kind of exploit chain though. My PoC does everything for you except symbolic link/file creation once the report gets removed by the cron script, you have to create it manually then press enter to resume Apport and let the chown happen. You could also create a new crontab entry and directory then copy Apport cron script into it so you don't have to wait the entire day. Fix suggestions: - Use reportfile.fileno() instead of the report string for the os.chown call, and also add follow_symlinks=False argument just in case. - Remove the size 0 condition in the cron script (not sure about this one since the condition was there for a purpose). Vulnerable code (from data/apport):    700 # we prefer having a file mode of 0 while writing; this doesn't work    701 # for suid binaries as we completely drop privs and thus can't chmod    702 # them later on    703 if pidstat.st_uid != os.getuid():    704 mode = 0o640    705 else:    706 mode = 0    707 reportfile = os.fdopen(os.open(report, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode), 'w+b')    708 assert reportfile.fileno() > sys.stderr.fileno()    709    710 # Make sure the crash reporting daemon can read this report    711 try:    712 gid = pwd.getpwnam('whoopsie').pw_gid    713 os.chown(report, pidstat.st_uid, gid)    714 except (OSError, KeyError):    715 os.chown(report, pidstat.st_uid, pidstat.st_gid)    716 except (OSError, IOError) as e:    717 error_log('Could not create report file: %s' % str(e))    718 sys.exit(1) The TOCTTOU takes place between the os.open and the os.chown call and can be fully achieved thanks to the Apport cron script (etc/cron.daily/apport):      1 #!/bin/sh -e      2 # clean all crash reports which are older than a week.      3 [ -d /var/crash ] || exit 0      4 find /var/crash/. ! -name . -prune -type f \( \( -size 0 -a \! -name '*.upload*' -a \! -name '*.drkonqi*' \) -o -mtime +7 \) -exec rm -f -- '{}' \;      5 find /var/crash/. ! -name . -prune -type d -regextype posix-extended -regex '.*/[0-9]{12}$' \( -mtime +7 \) -exec rm -Rf -- '{}' \; The interesting part in this daily script is that the crash reports gets removed if their size is 0. Since Apport drops real uid and gid, the crashed process owner can send signals during the report file creation. At this time, effective uid and gid are still root. We can also block Apport by replacing user settings file with a FIFO (~/.config/apport/settings). I'm using the FIFO way in my PoC but it can be done without it. To make Apport read user settings, the crashing program must not be located in one of those directories (taken from apport/fileutils.py):     78 pkg_whitelist = ['/bin/', '/boot', '/etc/', '/initrd', '/lib', '/sbin/',     79 '/opt', '/usr/', '/var'] # packages only ship executables in these directories Once Apport is blocked into FIFO reading, we send the SIGSTOP signal then we write "[main]\nunpackaged=1" into the FIFO so Apport won't exit after resuming (if unpackaged is 0 Apport directly exits because we're not in a "packaged" directory). After that we "single step" through Apport by sending SIGCONT and SIGSTOP consecutively in a loop until the report file is created with os.open. We must make sure os.chown hasn't been called and then we wait for the cron script to remove the report (it's created as root with mode 0 so only root can remove it). Once removed, we can replace it with a symbolic link/file of the same name, resume Apport with SIGCONT then the file will now be owned by the crashed process user and group. I think the impact of this vulnerability alone is low because fs.protected_symlinks prevents symlink resolution since we're in a sticky world writable directory (/var/crash), but if it's disabled, you can escalate privileges very easily. It still can be used in some kind of exploit chain though. My PoC does everything for you except symbolic link/file creation once the report gets removed by the cron script, you have to create it manually then press enter to resume Apport and let the chown happen. You could also create a new crontab entry and directory then copy Apport cron script into it so you don't have to wait the entire day. Fix suggestions: - Use reportfile.fileno() instead of the report string for the os.chown calls, and also add follow_symlinks=False argument just in case. - Remove the size 0 condition in the cron script (not sure about this one, I suppose the condition was there for a reason).
2020-02-14 20:04:57 Seth Arnold cve linked 2020-8833
2020-02-27 05:04:14 Alex Murray attachment added apport_2.20.11-0ubuntu19.debdiff https://bugs.launchpad.net/ubuntu/+source/apport/+bug/1862933/+attachment/5331454/+files/apport_2.20.11-0ubuntu19.debdiff
2020-02-28 05:25:22 Alex Murray attachment added apport_2.20.11-0ubuntu19.debdiff https://bugs.launchpad.net/ubuntu/+source/apport/+bug/1862933/+attachment/5331792/+files/apport_2.20.11-0ubuntu19.debdiff
2020-04-02 00:43:37 Launchpad Janitor apport (Ubuntu): status New Fix Released
2020-04-02 00:43:37 Launchpad Janitor cve linked 2020-8831
2020-04-02 03:04:55 Alex Murray information type Private Security Public Security
2020-04-02 20:11:06 Launchpad Janitor branch linked lp:~ubuntu-core-dev/ubuntu/focal/apport/ubuntu
2022-06-27 10:17:11 Benjamin Drung apport: milestone 2.21.0
2022-06-27 10:17:14 Benjamin Drung apport: status New Fix Released
2022-06-27 10:17:16 Benjamin Drung apport: importance Undecided Critical