|
|
8ec399 |
From a4e47c753e9d7988f4d938ed2e0fd690a909ce68 Mon Sep 17 00:00:00 2001
|
|
|
8ec399 |
From: Jakub Filak <jfilak@redhat.com>
|
|
|
8ec399 |
Date: Mon, 20 Apr 2015 15:15:40 +0200
|
|
|
8ec399 |
Subject: [ABRT PATCH] upload: validate and sanitize uploaded dump directories
|
|
|
8ec399 |
|
|
|
8ec399 |
It was discovered that, when moving problem reports from
|
|
|
8ec399 |
/var/spool/abrt-upload to /var/spool/abrt or /var/tmp/abrt,
|
|
|
8ec399 |
abrt-handle-upload does not verify that the new problem directory
|
|
|
8ec399 |
has appropriate permissions and does not contain symbolic links. A
|
|
|
8ec399 |
crafted problem report exposes other parts of abrt to attack, and
|
|
|
8ec399 |
the abrt-handle-upload script allows to overwrite arbitrary files.
|
|
|
8ec399 |
|
|
|
8ec399 |
Acknowledgement:
|
|
|
8ec399 |
|
|
|
8ec399 |
This issue was discovered by Florian Weimer of Red Hat Product Security.
|
|
|
8ec399 |
|
|
|
8ec399 |
Related: #1212953
|
|
|
8ec399 |
|
|
|
8ec399 |
Signed-off-by: Jakub Filak <jfilak@redhat.com>
|
|
|
8ec399 |
---
|
|
|
8ec399 |
src/daemon/abrt-handle-upload.in | 78 +++++++++++++++++++++++++++++++++++-----
|
|
|
8ec399 |
1 file changed, 70 insertions(+), 8 deletions(-)
|
|
|
8ec399 |
|
|
|
8ec399 |
diff --git a/src/daemon/abrt-handle-upload.in b/src/daemon/abrt-handle-upload.in
|
|
|
8ec399 |
index dbc4534..7720da4 100755
|
|
|
8ec399 |
--- a/src/daemon/abrt-handle-upload.in
|
|
|
8ec399 |
+++ b/src/daemon/abrt-handle-upload.in
|
|
|
8ec399 |
@@ -10,6 +10,7 @@ import getopt
|
|
|
8ec399 |
import tempfile
|
|
|
8ec399 |
import shutil
|
|
|
8ec399 |
import datetime
|
|
|
8ec399 |
+import grp
|
|
|
8ec399 |
|
|
|
8ec399 |
from reportclient import set_verbosity, error_msg_and_die, error_msg, log
|
|
|
8ec399 |
|
|
|
8ec399 |
@@ -36,12 +37,77 @@ def init_gettext():
|
|
|
8ec399 |
|
|
|
8ec399 |
import problem
|
|
|
8ec399 |
|
|
|
8ec399 |
-def write_str_to(filename, s):
|
|
|
8ec399 |
- fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, @DEFAULT_DUMP_DIR_MODE@ | stat.S_IROTH)
|
|
|
8ec399 |
+def write_str_to(filename, s, uid, gid, mode):
|
|
|
8ec399 |
+ fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode)
|
|
|
8ec399 |
if fd >= 0:
|
|
|
8ec399 |
+ os.fchown(fd, uid, gid)
|
|
|
8ec399 |
os.write(fd, s)
|
|
|
8ec399 |
os.close(fd)
|
|
|
8ec399 |
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+def validate_transform_move_and_notify(uploaded_dir_path, problem_dir_path, dest=None):
|
|
|
8ec399 |
+ fsuid = 0
|
|
|
8ec399 |
+ fsgid = 0
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+ try:
|
|
|
8ec399 |
+ gabrt = grp.getgrnam("abrt")
|
|
|
8ec399 |
+ fsgid = gabrt.gr_gid
|
|
|
8ec399 |
+ except KeyError as ex:
|
|
|
8ec399 |
+ error_msg("Failed to get GID of 'abrt' (using 0 instead): {0}'".format(str(ex)))
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+ try:
|
|
|
8ec399 |
+ # give the uploaded directory to 'root:abrt' or 'root:root'
|
|
|
8ec399 |
+ os.chown(uploaded_dir_path, fsuid, fsgid)
|
|
|
8ec399 |
+ # set the right permissions for this machine
|
|
|
8ec399 |
+ # (allow the owner and the group to access problem elements,
|
|
|
8ec399 |
+ # the default dump dir mode lacks x bit for both)
|
|
|
8ec399 |
+ os.chmod(uploaded_dir_path, @DEFAULT_DUMP_DIR_MODE@ | stat.S_IXUSR | stat.S_IXGRP)
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+ # sanitize problem elements
|
|
|
8ec399 |
+ for item in os.listdir(uploaded_dir_path):
|
|
|
8ec399 |
+ apath = os.path.join(uploaded_dir_path, item)
|
|
|
8ec399 |
+ if os.path.islink(apath):
|
|
|
8ec399 |
+ # remove symbolic links
|
|
|
8ec399 |
+ os.remove(apath)
|
|
|
8ec399 |
+ elif os.path.isdir(apath):
|
|
|
8ec399 |
+ # remove directories
|
|
|
8ec399 |
+ shutil.rmtree(apath)
|
|
|
8ec399 |
+ elif os.path.isfile(apath):
|
|
|
8ec399 |
+ # set file ownership to 'root:abrt' or 'root:root'
|
|
|
8ec399 |
+ os.chown(apath, fsuid, fsgid)
|
|
|
8ec399 |
+ # set the right file permissions for this machine
|
|
|
8ec399 |
+ os.chmod(apath, @DEFAULT_DUMP_DIR_MODE@)
|
|
|
8ec399 |
+ else:
|
|
|
8ec399 |
+ # remove things that are neither files, symlinks nor directories
|
|
|
8ec399 |
+ os.remove(apath)
|
|
|
8ec399 |
+ except OSError as ex:
|
|
|
8ec399 |
+ error_msg("Removing uploaded dir '{0}': '{1}'".format(uploaded_dir_path, str(ex)))
|
|
|
8ec399 |
+ try:
|
|
|
8ec399 |
+ shutil.rmtree(uploaded_dir_path)
|
|
|
8ec399 |
+ except OSError as ex2:
|
|
|
8ec399 |
+ error_msg_and_die("Failed to clean up dir '{0}': '{1}'".format(uploaded_dir_path, str(ex2)))
|
|
|
8ec399 |
+ return
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+ # overwrite remote if it exists
|
|
|
8ec399 |
+ remote_path = os.path.join(uploaded_dir_path, "remote")
|
|
|
8ec399 |
+ write_str_to(remote_path, "1", fsuid, fsgid, @DEFAULT_DUMP_DIR_MODE@)
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+ # abrtd would increment count value and abrt-server refuses to process
|
|
|
8ec399 |
+ # problem directories containing 'count' element when PrivateReports is on.
|
|
|
8ec399 |
+ count_path = os.path.join(uploaded_dir_path, "count")
|
|
|
8ec399 |
+ if os.path.exists(count_path):
|
|
|
8ec399 |
+ # overwrite remote_count if it exists
|
|
|
8ec399 |
+ remote_count_path = os.path.join(uploaded_dir_path, "remote_count")
|
|
|
8ec399 |
+ os.rename(count_path, remote_count_path)
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+ if not dest:
|
|
|
8ec399 |
+ dest = problem_dir_path
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+ shutil.move(uploaded_dir_path, dest)
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+ problem.notify_new_path(problem_dir_path)
|
|
|
8ec399 |
+
|
|
|
8ec399 |
+
|
|
|
8ec399 |
if __name__ == "__main__":
|
|
|
8ec399 |
|
|
|
8ec399 |
# Helper: exit with cleanup
|
|
|
8ec399 |
@@ -177,21 +243,17 @@ if __name__ == "__main__":
|
|
|
8ec399 |
# or one or more complete problem data directories.
|
|
|
8ec399 |
# Checking second possibility first.
|
|
|
8ec399 |
if os.path.exists(tempdir+"/analyzer") and os.path.exists(tempdir+"/time"):
|
|
|
8ec399 |
- write_str_to(tempdir+"/remote", "1")
|
|
|
8ec399 |
- shutil.move(tempdir, abrt_dir)
|
|
|
8ec399 |
- problem.notify_new_path(abrt_dir+"/"+os.path.basename(tempdir))
|
|
|
8ec399 |
+ validate_transform_move_and_notify(tempdir, abrt_dir+"/"+os.path.basename(tempdir), dest=abrt_dir)
|
|
|
8ec399 |
else:
|
|
|
8ec399 |
for d in os.listdir(tempdir):
|
|
|
8ec399 |
if not os.path.isdir(tempdir+"/"+d):
|
|
|
8ec399 |
continue
|
|
|
8ec399 |
- write_str_to(tempdir+"/"+d+"/remote", "1")
|
|
|
8ec399 |
dst = abrt_dir+"/"+d
|
|
|
8ec399 |
if os.path.exists(dst):
|
|
|
8ec399 |
dst += "."+str(os.getpid())
|
|
|
8ec399 |
if os.path.exists(dst):
|
|
|
8ec399 |
continue
|
|
|
8ec399 |
- shutil.move(tempdir+"/"+d, dst)
|
|
|
8ec399 |
- problem.notify_new_path(dst)
|
|
|
8ec399 |
+ validate_transform_move_and_notify(tempdir+"/"+d, dst)
|
|
|
8ec399 |
|
|
|
8ec399 |
die_exitcode = 0
|
|
|
8ec399 |
# This deletes working_dir (== delete_on_exit)
|
|
|
8ec399 |
--
|
|
|
8ec399 |
1.8.3.1
|
|
|
8ec399 |
|