VYPR
Unrated severityNVD Advisory· Published Jan 14, 2020· Updated Aug 6, 2024

CVE-2015-3147

CVE-2015-3147

Description

ABRT does not validate uploaded problem reports, enabling local symlink attacks to write arbitrary files.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

ABRT does not validate uploaded problem reports, enabling local symlink attacks to write arbitrary files.

Vulnerability

In the Automatic Bug Reporting Tool (ABRT) prior to version 2.1.11-22 on Red Hat Enterprise Linux 7, the abrt-handle-upload daemon failed to validate the contents of uploaded problem reports when moving them from /var/spool/abrt-upload. This allowed a local user to create a symbolic link within the uploaded directory, pointing to an arbitrary file on the system. The vulnerable code path is reached when the ABRT upload service is enabled and the attacker has the ability to upload a crafted problem report. The affected version is ABRT 2.1.11-22 and earlier on RHEL 7 [1][2].

Exploitation

An attacker must have local access and the ability to upload a problem report to /var/spool/abrt-upload. By crafting an uploaded directory that contains a symbolic link (symlink) pointing to a target file (e.g., /etc/passwd or another critical system file), and then triggering the upload processing, ABRT will follow the symlink when moving files to /var/spool/abrt or /var/tmp/abrt, thereby writing to the attacker-chosen destination. No authentication is required beyond local user access, and no user interaction is needed if the upload is automatically processed [2][4].

Impact

A successful attack allows a local user to write to arbitrary files on the system, potentially overwriting sensitive configuration files or system binaries. This could lead to privilege escalation, denial of service, or other unspecified impacts. The compromise occurs with the privileges of the ABRT daemon (typically root), so the attacker can achieve root-level write access to arbitrary files [2][4].

Mitigation

The vulnerability is fixed in ABRT version 2.1.11-22.el7_1, released as part of Red Hat Security Advisory RHSA-2015:1083 on June 16, 2015 [1]. The fix includes validation and sanitization of uploaded problem directories, removing symlinks and directories, and enforcing correct file ownership and permissions [3]. Workarounds include disabling the ABRT upload service if not needed. ABRT is not listed on CISA's Known Exploited Vulnerabilities (KEV) catalog as of the publication date [1][2].

AI Insight generated on May 24, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

2
3746b7627218

upload: validate and sanitize uploaded dump directories

https://github.com/abrt/abrtJakub FilakApr 20, 2015via nvd-ref
1 file changed · +70 8
  • src/daemon/abrt-handle-upload.in+70 8 modified
    @@ -10,6 +10,7 @@ import getopt
     import tempfile
     import shutil
     import datetime
    +import grp
     
     from reportclient import set_verbosity, error_msg_and_die, error_msg, log
     
    @@ -36,12 +37,77 @@ def init_gettext():
     
     import problem
     
    -def write_str_to(filename, s):
    -    fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, @DEFAULT_DUMP_DIR_MODE@ | stat.S_IROTH)
    +def write_str_to(filename, s, uid, gid, mode):
    +    fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode)
         if fd >= 0:
    +        os.fchown(fd, uid, gid)
             os.write(fd, s)
             os.close(fd)
     
    +
    +def validate_transform_move_and_notify(uploaded_dir_path, problem_dir_path, dest=None):
    +    fsuid = 0
    +    fsgid = 0
    +
    +    try:
    +        gabrt = grp.getgrnam("abrt")
    +        fsgid = gabrt.gr_gid
    +    except KeyError as ex:
    +        error_msg("Failed to get GID of 'abrt' (using 0 instead): {0}'".format(str(ex)))
    +
    +    try:
    +        # give the uploaded directory to 'root:abrt' or 'root:root'
    +        os.chown(uploaded_dir_path, fsuid, fsgid)
    +        # set the right permissions for this machine
    +        # (allow the owner and the group to access problem elements,
    +        #  the default dump dir mode lacks x bit for both)
    +        os.chmod(uploaded_dir_path, @DEFAULT_DUMP_DIR_MODE@ | stat.S_IXUSR | stat.S_IXGRP)
    +
    +        # sanitize problem elements
    +        for item in os.listdir(uploaded_dir_path):
    +            apath = os.path.join(uploaded_dir_path, item)
    +            if os.path.islink(apath):
    +                # remove symbolic links
    +                os.remove(apath)
    +            elif os.path.isdir(apath):
    +                # remove directories
    +                shutil.rmtree(apath)
    +            elif os.path.isfile(apath):
    +                # set file ownership to 'root:abrt' or 'root:root'
    +                os.chown(apath, fsuid, fsgid)
    +                # set the right file permissions for this machine
    +                os.chmod(apath, @DEFAULT_DUMP_DIR_MODE@)
    +            else:
    +                # remove things that are neither files, symlinks nor directories
    +                os.remove(apath)
    +    except OSError as ex:
    +        error_msg("Removing uploaded dir '{0}': '{1}'".format(uploaded_dir_path, str(ex)))
    +        try:
    +            shutil.rmtree(uploaded_dir_path)
    +        except OSError as ex2:
    +            error_msg_and_die("Failed to clean up dir '{0}': '{1}'".format(uploaded_dir_path, str(ex2)))
    +        return
    +
    +    # overwrite remote if it exists
    +    remote_path = os.path.join(uploaded_dir_path, "remote")
    +    write_str_to(remote_path, "1", fsuid, fsgid, @DEFAULT_DUMP_DIR_MODE@)
    +
    +    # abrtd would increment count value and abrt-server refuses to process
    +    # problem directories containing 'count' element when PrivateReports is on.
    +    count_path = os.path.join(uploaded_dir_path, "count")
    +    if os.path.exists(count_path):
    +        # overwrite remote_count if it exists
    +        remote_count_path = os.path.join(uploaded_dir_path, "remote_count")
    +        os.rename(count_path, remote_count_path)
    +
    +    if not dest:
    +        dest = problem_dir_path
    +
    +    shutil.move(uploaded_dir_path, dest)
    +
    +    problem.notify_new_path(problem_dir_path)
    +
    +
     if __name__ == "__main__":
     
         # Helper: exit with cleanup
    @@ -177,21 +243,17 @@ if __name__ == "__main__":
             # or one or more complete problem data directories.
             # Checking second possibility first.
             if (os.path.exists(tempdir+"/analyzer") or os.path.exists(tempdir+"/type")) and os.path.exists(tempdir+"/time"):
    -            write_str_to(tempdir+"/remote", "1")
    -            shutil.move(tempdir, abrt_dir)
    -            problem.notify_new_path(abrt_dir+"/"+os.path.basename(tempdir))
    +            validate_transform_move_and_notify(tempdir, abrt_dir+"/"+os.path.basename(tempdir), dest=abrt_dir)
             else:
                 for d in os.listdir(tempdir):
                     if not os.path.isdir(tempdir+"/"+d):
                         continue
    -                write_str_to(tempdir+"/"+d+"/remote", "1")
                     dst = abrt_dir+"/"+d
                     if os.path.exists(dst):
                         dst += "."+str(os.getpid())
                     if os.path.exists(dst):
                         continue
    -                shutil.move(tempdir+"/"+d, dst)
    -                problem.notify_new_path(dst)
    +                validate_transform_move_and_notify(tempdir+"/"+d, dst)
     
             die_exitcode = 0
             # This deletes working_dir (== delete_on_exit)
    
9e98b795c0f9

Merge 3746b7627218438ae7d781fc8b18a221454e9091 into f666bc2bd3017e921c6f08591c3e9723abc1d410

https://github.com/abrt/abrtJakub FilakMay 5, 2015via nvd-ref
1 file changed · +70 8
  • src/daemon/abrt-handle-upload.in+70 8 modified
    @@ -10,6 +10,7 @@ import getopt
     import tempfile
     import shutil
     import datetime
    +import grp
     
     from reportclient import set_verbosity, error_msg_and_die, error_msg, log
     
    @@ -36,12 +37,77 @@ def init_gettext():
     
     import problem
     
    -def write_str_to(filename, s):
    -    fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, @DEFAULT_DUMP_DIR_MODE@ | stat.S_IROTH)
    +def write_str_to(filename, s, uid, gid, mode):
    +    fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode)
         if fd >= 0:
    +        os.fchown(fd, uid, gid)
             os.write(fd, s)
             os.close(fd)
     
    +
    +def validate_transform_move_and_notify(uploaded_dir_path, problem_dir_path, dest=None):
    +    fsuid = 0
    +    fsgid = 0
    +
    +    try:
    +        gabrt = grp.getgrnam("abrt")
    +        fsgid = gabrt.gr_gid
    +    except KeyError as ex:
    +        error_msg("Failed to get GID of 'abrt' (using 0 instead): {0}'".format(str(ex)))
    +
    +    try:
    +        # give the uploaded directory to 'root:abrt' or 'root:root'
    +        os.chown(uploaded_dir_path, fsuid, fsgid)
    +        # set the right permissions for this machine
    +        # (allow the owner and the group to access problem elements,
    +        #  the default dump dir mode lacks x bit for both)
    +        os.chmod(uploaded_dir_path, @DEFAULT_DUMP_DIR_MODE@ | stat.S_IXUSR | stat.S_IXGRP)
    +
    +        # sanitize problem elements
    +        for item in os.listdir(uploaded_dir_path):
    +            apath = os.path.join(uploaded_dir_path, item)
    +            if os.path.islink(apath):
    +                # remove symbolic links
    +                os.remove(apath)
    +            elif os.path.isdir(apath):
    +                # remove directories
    +                shutil.rmtree(apath)
    +            elif os.path.isfile(apath):
    +                # set file ownership to 'root:abrt' or 'root:root'
    +                os.chown(apath, fsuid, fsgid)
    +                # set the right file permissions for this machine
    +                os.chmod(apath, @DEFAULT_DUMP_DIR_MODE@)
    +            else:
    +                # remove things that are neither files, symlinks nor directories
    +                os.remove(apath)
    +    except OSError as ex:
    +        error_msg("Removing uploaded dir '{0}': '{1}'".format(uploaded_dir_path, str(ex)))
    +        try:
    +            shutil.rmtree(uploaded_dir_path)
    +        except OSError as ex2:
    +            error_msg_and_die("Failed to clean up dir '{0}': '{1}'".format(uploaded_dir_path, str(ex2)))
    +        return
    +
    +    # overwrite remote if it exists
    +    remote_path = os.path.join(uploaded_dir_path, "remote")
    +    write_str_to(remote_path, "1", fsuid, fsgid, @DEFAULT_DUMP_DIR_MODE@)
    +
    +    # abrtd would increment count value and abrt-server refuses to process
    +    # problem directories containing 'count' element when PrivateReports is on.
    +    count_path = os.path.join(uploaded_dir_path, "count")
    +    if os.path.exists(count_path):
    +        # overwrite remote_count if it exists
    +        remote_count_path = os.path.join(uploaded_dir_path, "remote_count")
    +        os.rename(count_path, remote_count_path)
    +
    +    if not dest:
    +        dest = problem_dir_path
    +
    +    shutil.move(uploaded_dir_path, dest)
    +
    +    problem.notify_new_path(problem_dir_path)
    +
    +
     if __name__ == "__main__":
     
         # Helper: exit with cleanup
    @@ -177,21 +243,17 @@ if __name__ == "__main__":
             # or one or more complete problem data directories.
             # Checking second possibility first.
             if (os.path.exists(tempdir+"/analyzer") or os.path.exists(tempdir+"/type")) and os.path.exists(tempdir+"/time"):
    -            write_str_to(tempdir+"/remote", "1")
    -            shutil.move(tempdir, abrt_dir)
    -            problem.notify_new_path(abrt_dir+"/"+os.path.basename(tempdir))
    +            validate_transform_move_and_notify(tempdir, abrt_dir+"/"+os.path.basename(tempdir), dest=abrt_dir)
             else:
                 for d in os.listdir(tempdir):
                     if not os.path.isdir(tempdir+"/"+d):
                         continue
    -                write_str_to(tempdir+"/"+d+"/remote", "1")
                     dst = abrt_dir+"/"+d
                     if os.path.exists(dst):
                         dst += "."+str(os.getpid())
                     if os.path.exists(dst):
                         continue
    -                shutil.move(tempdir+"/"+d, dst)
    -                problem.notify_new_path(dst)
    +                validate_transform_move_and_notify(tempdir+"/"+d, dst)
     
             die_exitcode = 0
             # This deletes working_dir (== delete_on_exit)
    

Vulnerability mechanics

Root cause

"Missing validation of uploaded problem directories allows symbolic links to be followed during move, enabling arbitrary file overwrite."

Attack vector

A local attacker crafts a problem report directory inside `/var/spool/abrt-upload` containing a symbolic link pointing to an arbitrary file on the system (e.g. `/etc/passwd`). When `abrt-handle-upload` moves the uploaded directory to `/var/spool/abrt` or `/var/tmp/abrt`, the symlink is followed, allowing the attacker to overwrite arbitrary files. The advisory notes that the script "does not verify that the new problem directory has appropriate permissions and does not contain symbolic links" [ref_id=1]. No authentication is required beyond the ability to place files in the upload spool.

Affected code

The vulnerable script is `src/daemon/abrt-handle-upload.in`. The old code directly called `shutil.move()` on uploaded problem directories without inspecting their contents for symbolic links or improper permissions [patch_id=2247016]. The new `validate_transform_move_and_notify()` function was introduced to sanitize each uploaded directory before moving it.

What the fix does

The patch introduces `validate_transform_move_and_notify()` which, before moving the uploaded directory, changes ownership to `root:abrt` (or `root:root`), sets safe permissions, and iterates over every item in the directory [patch_id=2247016]. Symbolic links are removed via `os.remove()`, subdirectories are deleted via `shutil.rmtree()`, and regular files are re-owned and re-permissioned. This prevents symlink-based file overwrites and ensures only flat, safe file entries reach the destination.

Preconditions

  • inputAttacker must be able to write files into /var/spool/abrt-upload (the upload spool directory)
  • configThe abrt-handle-upload script must be triggered to process the uploaded directory

Generated on May 24, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.