diff options
author | Natanael Copa <ncopa@alpinelinux.org> | 2014-03-03 13:32:46 +0000 |
---|---|---|
committer | Natanael Copa <ncopa@alpinelinux.org> | 2014-03-03 13:32:46 +0000 |
commit | 925aedc7db715e20edabddbb624db5477441d4cf (patch) | |
tree | 38ec81f081dda0b72805fe4e1995b589360a1c72 /main | |
parent | 9876a50d3c6ce056eaf83310cda96485a8b2e850 (diff) | |
download | aports-925aedc7db715e20edabddbb624db5477441d4cf.tar.bz2 aports-925aedc7db715e20edabddbb624db5477441d4cf.tar.xz |
main/augeas: security fix for CVE-2012-0786 and CVE-2012-0787
fixes #2668
Diffstat (limited to 'main')
-rw-r--r-- | main/augeas/APKBUILD | 10 | ||||
-rw-r--r-- | main/augeas/CVE-2012-0786.patch | 617 | ||||
-rw-r--r-- | main/augeas/CVE-2012-0787.patch | 363 |
3 files changed, 987 insertions, 3 deletions
diff --git a/main/augeas/APKBUILD b/main/augeas/APKBUILD index a336a06364..bd2d937442 100644 --- a/main/augeas/APKBUILD +++ b/main/augeas/APKBUILD @@ -1,7 +1,7 @@ # Maintainer: Natanael Copa <ncopa@alpinelinux.org> pkgname=augeas pkgver=0.10.0 -pkgrel=2 +pkgrel=3 pkgdesc="a configuration editing tool" url="http://augeas.net" arch="all" @@ -11,9 +11,11 @@ depends_dev="libxml2-dev" makedepends="$depends_dev readline-dev" install= subpackages="$pkgname-dev $pkgname-doc $pkgname-tests" -source="http://augeas.net/download/augeas-$pkgver.tar.gz +source="http://download.augeas.net/archive/augeas-$pkgver.tar.gz regexp.c.patch augeas-requires-libxml2.patch + CVE-2012-0786.patch + CVE-2012-0787.patch " _builddir="$srcdir"/$pkgname-$pkgver @@ -49,4 +51,6 @@ tests() { } md5sums="fe1834e90a066c3208ac0214622c7352 augeas-0.10.0.tar.gz a9f0fe2cd7e6aebf916747207cc2b537 regexp.c.patch -ea212ab45cde19301646b974cc0941e8 augeas-requires-libxml2.patch" +ea212ab45cde19301646b974cc0941e8 augeas-requires-libxml2.patch +cc3ee289eeb0c3017c5aaa6c223841a2 CVE-2012-0786.patch +d9e5109ca89e09f952b67811b522bc44 CVE-2012-0787.patch" diff --git a/main/augeas/CVE-2012-0786.patch b/main/augeas/CVE-2012-0786.patch new file mode 100644 index 0000000000..8e44ca8acc --- /dev/null +++ b/main/augeas/CVE-2012-0786.patch @@ -0,0 +1,617 @@ +From 55048a57d6f0e5603fbf7f1e45e287df4c2a2c07 Mon Sep 17 00:00:00 2001 +From: Dominic Cleal <dcleal@redhat.com> +Date: Sun, 12 Feb 2012 21:41:37 +0000 +Subject: [PATCH] Prevent symlink attacks via .augnew during saving + +Instead of saving into a predictable PATH.augnew file, save into a securely +created PATH.augnew.XXXXXX + +* src/transform.c (transform_save): + write changes to a temporary file in the same directory as the destination + (either the file's canonical path or the path of .augnew), before renaming + +* src/transform.c (transfer_file_attrs): + use fchown, fchmod etc. on the same file handles to ensure consistent + permission changes + +* bootstrap: add mkstemp gnulib module +* tests/ + test-put-symlink-augnew.sh: test symlink attack when writing .augnew + test-put-symlink-augsave.sh: test symlink attack when writing .augsave + test-put-symlink-augtemp.sh: test symlink attack via temp .augnew + test-put-symlink.sh: also test file modification + +Fixes BZ 772257 + +Conflicts: + src/transform.c +--- + bootstrap | 1 + + src/internal.c | 15 +++- + src/internal.h | 3 + + src/transform.c | 141 ++++++++++++++++++++++++-------------- + tests/Makefile.am | 3 +- + tests/test-put-symlink-augnew.sh | 52 ++++++++++++++ + tests/test-put-symlink-augsave.sh | 52 ++++++++++++++ + tests/test-put-symlink-augtemp.sh | 52 ++++++++++++++ + tests/test-put-symlink.sh | 5 ++ + tests/test-save-empty.sh | 5 +- + 10 files changed, 270 insertions(+), 59 deletions(-) + create mode 100755 tests/test-put-symlink-augnew.sh + create mode 100755 tests/test-put-symlink-augsave.sh + create mode 100755 tests/test-put-symlink-augtemp.sh + +diff --git a/src/internal.c b/src/internal.c +index 566498d..44956f8 100644 +--- a/src/internal.c ++++ b/src/internal.c +@@ -125,8 +125,7 @@ fread_file_lim (FILE *stream, size_t max_len, size_t *length) + return NULL; + } + +-char* xread_file(const char *path) { +- FILE *fp = fopen(path, "r"); ++char* xfread_file(FILE *fp) { + char *result; + size_t len; + +@@ -134,7 +133,6 @@ char* xread_file(const char *path) { + return NULL; + + result = fread_file_lim(fp, MAX_READ_LEN, &len); +- fclose (fp); + + if (result != NULL + && len <= MAX_READ_LEN +@@ -145,6 +143,17 @@ char* xread_file(const char *path) { + return NULL; + } + ++char* xread_file(const char *path) { ++ FILE *fp; ++ char *result; ++ ++ fp = fopen(path, "r"); ++ result = xfread_file(fp); ++ fclose (fp); ++ ++ return result; ++} ++ + /* + * Escape/unescape of string literals + */ +diff --git a/src/internal.h b/src/internal.h +index 7317842..a77ad28 100644 +--- a/src/internal.h ++++ b/src/internal.h +@@ -283,6 +283,9 @@ char *format_pos(const char *text, int pos); + */ + char* xread_file(const char *path); + ++/* Like xread_file, but caller supplies a file pointer */ ++char* xfread_file(FILE *fp); ++ + /* Get the error message for ERRNUM in a threadsafe way. Based on libvirt's + * virStrError + */ +diff --git a/src/transform.c b/src/transform.c +index b36b944..0936eb7 100644 +--- a/src/transform.c ++++ b/src/transform.c +@@ -739,35 +739,38 @@ int transform_applies(struct tree *xfm, const char *path) { + return filter_matches(xfm, path + strlen(AUGEAS_FILES_TREE)); + } + +-static int transfer_file_attrs(const char *from, const char *to, ++static int transfer_file_attrs(FILE *from, FILE *to, + const char **err_status) { + struct stat st; + int ret = 0; + int selinux_enabled = (is_selinux_enabled() > 0); + security_context_t con = NULL; + +- ret = lstat(from, &st); ++ int from_fd = fileno(from); ++ int to_fd = fileno(to); ++ ++ ret = fstat(from_fd, &st); + if (ret < 0) { + *err_status = "replace_stat"; + return -1; + } + if (selinux_enabled) { +- if (lgetfilecon(from, &con) < 0 && errno != ENOTSUP) { ++ if (fgetfilecon(from_fd, &con) < 0 && errno != ENOTSUP) { + *err_status = "replace_getfilecon"; + return -1; + } + } + +- if (lchown(to, st.st_uid, st.st_gid) < 0) { ++ if (fchown(to_fd, st.st_uid, st.st_gid) < 0) { + *err_status = "replace_chown"; + return -1; + } +- if (chmod(to, st.st_mode) < 0) { ++ if (fchmod(to_fd, st.st_mode) < 0) { + *err_status = "replace_chmod"; + return -1; + } + if (selinux_enabled && con != NULL) { +- if (lsetfilecon(to, con) < 0 && errno != ENOTSUP) { ++ if (fsetfilecon(to_fd, con) < 0 && errno != ENOTSUP) { + *err_status = "replace_setfilecon"; + return -1; + } +@@ -810,7 +813,7 @@ static int clone_file(const char *from, const char *to, + goto done; + } + +- if (transfer_file_attrs(from, to, err_status) < 0) ++ if (transfer_file_attrs(from_fp, to_fp, err_status) < 0) + goto done; + + while ((len = fread(buf, 1, BUFSIZ, from_fp)) > 0) { +@@ -887,19 +890,38 @@ static int file_saved_event(struct augeas *aug, const char *path) { + * are noted in the /augeas/files hierarchy in AUG->ORIGIN under + * PATH/error. + * +- * Writing the file happens by first writing into PATH.augnew, transferring +- * all file attributes of PATH to PATH.augnew, and then renaming +- * PATH.augnew to PATH. If the rename fails, and the entry +- * AUGEAS_COPY_IF_FAILURE exists in AUG->ORIGIN, PATH is overwritten by +- * copying file contents ++ * Writing the file happens by first writing into a temp file, transferring all ++ * file attributes of PATH to the temp file, and then renaming the temp file ++ * back to PATH. ++ * ++ * Temp files are created alongside the destination file to enable the rename, ++ * which may be the canonical path (PATH_canon) if PATH is a symlink. ++ * ++ * If the AUG_SAVE_NEWFILE flag is set, instead rename to PATH.augnew rather ++ * than PATH. If AUG_SAVE_BACKUP is set, move the original to PATH.augsave. ++ * (Always PATH.aug{new,save} irrespective of whether PATH is a symlink.) ++ * ++ * If the rename fails, and the entry AUGEAS_COPY_IF_FAILURE exists in ++ * AUG->ORIGIN, PATH is instead overwritten by copying file contents. ++ * ++ * The table below shows the locations for each permutation. ++ * ++ * PATH save flag temp file dest file backup? ++ * regular - PATH.augnew.XXXX PATH - ++ * regular BACKUP PATH.augnew.XXXX PATH PATH.augsave ++ * regular NEWFILE PATH.augnew.XXXX PATH.augnew - ++ * symlink - PATH_canon.XXXX PATH_canon - ++ * symlink BACKUP PATH_canon.XXXX PATH_canon PATH.augsave ++ * symlink NEWFILE PATH.augnew.XXXX PATH.augnew - + * + * Return 0 on success, -1 on failure. + */ + int transform_save(struct augeas *aug, struct tree *xfm, + const char *path, struct tree *tree) { +- FILE *fp = NULL; +- char *augnew = NULL, *augorig = NULL, *augsave = NULL; +- char *augorig_canon = NULL; ++ int fd; ++ FILE *fp = NULL, *augorig_canon_fp = NULL; ++ char *augtemp = NULL, *augnew = NULL, *augorig = NULL, *augsave = NULL; ++ char *augorig_canon = NULL, *augdest = NULL; + int augorig_exists; + int copy_if_rename_fails = 0; + char *text = NULL; +@@ -926,19 +948,6 @@ int transform_save(struct augeas *aug, struct tree *xfm, + goto done; + } + +- if (access(augorig, R_OK) == 0) { +- text = xread_file(augorig); +- } else { +- text = strdup(""); +- } +- +- if (text == NULL) { +- err_status = "put_read"; +- goto done; +- } +- +- text = append_newline(text, strlen(text)); +- + augorig_canon = canonicalize_file_name(augorig); + augorig_exists = 1; + if (augorig_canon == NULL) { +@@ -951,31 +960,53 @@ int transform_save(struct augeas *aug, struct tree *xfm, + } + } + +- /* Figure out where to put the .augnew file. If we need to rename it +- later on, put it next to augorig_canon */ ++ if (access(augorig_canon, R_OK) == 0) { ++ augorig_canon_fp = fopen(augorig_canon, "r"); ++ text = xfread_file(augorig_canon_fp); ++ } else { ++ text = strdup(""); ++ } ++ ++ if (text == NULL) { ++ err_status = "put_read"; ++ goto done; ++ } ++ ++ text = append_newline(text, strlen(text)); ++ ++ /* Figure out where to put the .augnew and temp file. If no .augnew file ++ then put the temp file next to augorig_canon, else next to .augnew. */ + if (aug->flags & AUG_SAVE_NEWFILE) { + if (xasprintf(&augnew, "%s" EXT_AUGNEW, augorig) < 0) { + err_status = "augnew_oom"; + goto done; + } ++ augdest = augnew; + } else { +- if (xasprintf(&augnew, "%s" EXT_AUGNEW, augorig_canon) < 0) { +- err_status = "augnew_oom"; +- goto done; +- } ++ augdest = augorig_canon; ++ } ++ ++ if (xasprintf(&augtemp, "%s.XXXXXX", augdest) < 0) { ++ err_status = "augtemp_oom"; ++ goto done; + } + + // FIXME: We might have to create intermediate directories + // to be able to write augnew, but we have no idea what permissions + // etc. they should get. Just the process default ? +- fp = fopen(augnew, "w"); ++ fd = mkstemp(augtemp); ++ if (fd < 0) { ++ err_status = "mk_augtemp"; ++ goto done; ++ } ++ fp = fdopen(fd, "w"); + if (fp == NULL) { +- err_status = "open_augnew"; ++ err_status = "open_augtemp"; + goto done; + } + + if (augorig_exists) { +- if (transfer_file_attrs(augorig_canon, augnew, &err_status) != 0) { ++ if (transfer_file_attrs(augorig_canon_fp, fp, &err_status) != 0) { + err_status = "xfer_attrs"; + goto done; + } +@@ -985,22 +1016,22 @@ int transform_save(struct augeas *aug, struct tree *xfm, + lns_put(fp, lens, tree->children, text, &err); + + if (ferror(fp)) { +- err_status = "error_augnew"; ++ err_status = "error_augtemp"; + goto done; + } + + if (fflush(fp) != 0) { +- err_status = "flush_augnew"; ++ err_status = "flush_augtemp"; + goto done; + } + + if (fsync(fileno(fp)) < 0) { +- err_status = "sync_augnew"; ++ err_status = "sync_augtemp"; + goto done; + } + + if (fclose(fp) != 0) { +- err_status = "close_augnew"; ++ err_status = "close_augtemp"; + fp = NULL; + goto done; + } +@@ -1009,33 +1040,33 @@ int transform_save(struct augeas *aug, struct tree *xfm, + + if (err != NULL) { + err_status = "put_failed"; +- unlink(augnew); ++ unlink(augtemp); + goto done; + } + + { +- char *new_text = xread_file(augnew); ++ char *new_text = xread_file(augtemp); + int same = 0; + if (new_text == NULL) { +- err_status = "read_augnew"; ++ err_status = "read_augtemp"; + goto done; + } + same = STREQ(text, new_text); + FREE(new_text); + if (same) { + result = 0; +- unlink(augnew); ++ unlink(augtemp); + goto done; + } else if (aug->flags & AUG_SAVE_NOOP) { + result = 1; +- unlink(augnew); ++ unlink(augtemp); + goto done; + } + } + + if (!(aug->flags & AUG_SAVE_NEWFILE)) { + if (augorig_exists && (aug->flags & AUG_SAVE_BACKUP)) { +- r = asprintf(&augsave, "%s%s" EXT_AUGSAVE, aug->root, filename); ++ r = xasprintf(&augsave, "%s" EXT_AUGSAVE, augorig); + if (r == -1) { + augsave = NULL; + goto done; +@@ -1047,13 +1078,14 @@ int transform_save(struct augeas *aug, struct tree *xfm, + goto done; + } + } +- r = clone_file(augnew, augorig_canon, &err_status, +- copy_if_rename_fails); +- if (r != 0) { +- dyn_err_status = strappend(err_status, "_augnew"); +- goto done; +- } + } ++ ++ r = clone_file(augtemp, augdest, &err_status, copy_if_rename_fails); ++ if (r != 0) { ++ dyn_err_status = strappend(err_status, "_augtemp"); ++ goto done; ++ } ++ + result = 1; + + done: +@@ -1077,6 +1109,7 @@ int transform_save(struct augeas *aug, struct tree *xfm, + free(dyn_err_status); + lens_release(lens); + free(text); ++ free(augtemp); + free(augnew); + if (augorig_canon != augorig) + free(augorig_canon); +@@ -1086,6 +1119,8 @@ int transform_save(struct augeas *aug, struct tree *xfm, + + if (fp != NULL) + fclose(fp); ++ if (augorig_canon_fp != NULL) ++ fclose(augorig_canon_fp); + return result; + } + +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 817f368..3af1a0e 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -151,7 +151,8 @@ check_SCRIPTS = \ + test-interpreter.sh \ + $(lens_tests) \ + test-get.sh test-augtool.sh \ +- test-put-symlink.sh test-save-empty.sh \ ++ test-put-symlink.sh test-put-symlink-augnew.sh \ ++ test-put-symlink-augsave.sh test-put-symlink-augtemp.sh test-save-empty.sh \ + test-bug-1.sh test-idempotent.sh test-preserve.sh \ + test-events-saved.sh test-save-mode.sh test-unlink-error.sh \ + test-augtool-empty-line.sh test-augtool-modify-root.sh +diff --git a/tests/test-put-symlink-augnew.sh b/tests/test-put-symlink-augnew.sh +new file mode 100755 +index 0000000..eb361df +--- /dev/null ++++ b/tests/test-put-symlink-augnew.sh +@@ -0,0 +1,52 @@ ++#! /bin/bash ++ ++# Test that we don't follow symlinks when writing to .augnew ++ ++ROOT=$abs_top_builddir/build/test-put-symlink-augnew ++LENSES=$abs_top_srcdir/lenses ++ ++HOSTS=$ROOT/etc/hosts ++HOSTS_AUGNEW=${HOSTS}.augnew ++ ++ATTACK_FILE=$ROOT/other/attack ++ ++rm -rf $ROOT ++mkdir -p $(dirname $HOSTS) ++mkdir -p $(dirname $ATTACK_FILE) ++ ++cat <<EOF > $HOSTS ++127.0.0.1 localhost ++EOF ++touch $ATTACK_FILE ++ ++(cd $(dirname $HOSTS) && ln -s ../other/attack $(basename $HOSTS).augnew) ++ ++HOSTS_SUM=$(sum $HOSTS) ++ ++augtool --nostdinc -I $LENSES -r $ROOT --new > /dev/null <<EOF ++set /files/etc/hosts/1/alias myhost ++save ++EOF ++ ++if [ ! -f $HOSTS ] ; then ++ echo "/etc/hosts is no longer a regular file" ++ exit 1 ++fi ++if [ ! "x${HOSTS_SUM}" = "x$(sum $HOSTS)" ]; then ++ echo "/etc/hosts has changed" ++ exit 1 ++fi ++ ++if [ ! -f $HOSTS_AUGNEW ] ; then ++ echo "/etc/hosts.augnew is still a symlink, should be unlinked" ++ exit 1 ++fi ++if ! grep myhost $HOSTS_AUGNEW >/dev/null; then ++ echo "/etc/hosts does not contain the modification" ++ exit 1 ++fi ++ ++if [ -s $ATTACK_FILE ]; then ++ echo "/other/attack now contains data, should be blank" ++ exit 1 ++fi +diff --git a/tests/test-put-symlink-augsave.sh b/tests/test-put-symlink-augsave.sh +new file mode 100755 +index 0000000..8b4dbcf +--- /dev/null ++++ b/tests/test-put-symlink-augsave.sh +@@ -0,0 +1,52 @@ ++#! /bin/bash ++ ++# Test that we don't follow .augsave symlinks ++ ++ROOT=$abs_top_builddir/build/test-put-symlink-augsave ++LENSES=$abs_top_srcdir/lenses ++ ++HOSTS=$ROOT/etc/hosts ++HOSTS_AUGSAVE=${HOSTS}.augsave ++ ++ATTACK_FILE=$ROOT/other/attack ++ ++rm -rf $ROOT ++mkdir -p $(dirname $HOSTS) ++mkdir -p $(dirname $ATTACK_FILE) ++ ++cat <<EOF > $HOSTS ++127.0.0.1 localhost ++EOF ++HOSTS_SUM=$(sum $HOSTS) ++ ++touch $ATTACK_FILE ++(cd $(dirname $HOSTS) && ln -s ../other/attack $(basename $HOSTS).augsave) ++ ++# Now ask for the original to be saved in .augsave ++augtool --nostdinc -I $LENSES -r $ROOT --backup > /dev/null <<EOF ++set /files/etc/hosts/1/alias myhost ++save ++EOF ++ ++if [ ! -f $HOSTS ] ; then ++ echo "/etc/hosts is no longer a regular file" ++ exit 1 ++fi ++if [ ! -f $HOSTS_AUGNEW ] ; then ++ echo "/etc/hosts.augsave is still a symlink, should be unlinked" ++ exit 1 ++fi ++ ++if [ ! "x${HOSTS_SUM}" = "x$(sum $HOSTS_AUGSAVE)" ]; then ++ echo "/etc/hosts.augsave has changed from the original /etc/hosts" ++ exit 1 ++fi ++if ! grep myhost $HOSTS >/dev/null; then ++ echo "/etc/hosts does not contain the modification" ++ exit 1 ++fi ++ ++if [ -s $ATTACK_FILE ]; then ++ echo "/other/attack now contains data, should be blank" ++ exit 1 ++fi +diff --git a/tests/test-put-symlink-augtemp.sh b/tests/test-put-symlink-augtemp.sh +new file mode 100755 +index 0000000..0076e64 +--- /dev/null ++++ b/tests/test-put-symlink-augtemp.sh +@@ -0,0 +1,52 @@ ++#! /bin/bash ++ ++# Test that we don't follow .augnew symlinks (regression test) ++ ++ROOT=$abs_top_builddir/build/test-put-symlink-augtemp ++LENSES=$abs_top_srcdir/lenses ++ ++HOSTS=$ROOT/etc/hosts ++HOSTS_AUGNEW=${HOSTS}.augnew ++ ++ATTACK_FILE=$ROOT/other/attack ++ ++rm -rf $ROOT ++mkdir -p $(dirname $HOSTS) ++mkdir -p $(dirname $ATTACK_FILE) ++ ++cat <<EOF > $HOSTS ++127.0.0.1 localhost ++EOF ++touch $ATTACK_FILE ++ ++(cd $(dirname $HOSTS) && ln -s ../other/attack $(basename $HOSTS).augnew) ++ ++# Test the normal save code path which would use a temp augnew file ++augtool --nostdinc -I $LENSES -r $ROOT > /dev/null <<EOF ++set /files/etc/hosts/1/alias myhost1 ++save ++EOF ++ ++if [ -h $HOSTS ] ; then ++ echo "/etc/hosts is now a symlink, pointing to" $(readlink $HOSTS) ++ exit 1 ++fi ++if ! grep myhost1 $HOSTS >/dev/null; then ++ echo "/etc/hosts does not contain the modification" ++ exit 1 ++fi ++ ++if [ ! -h $HOSTS_AUGNEW ] ; then ++ echo "/etc/hosts.augnew is not a symbolic link" ++ exit 1 ++fi ++LINK=$(readlink $HOSTS_AUGNEW) ++if [ "x$LINK" != "x../other/attack" ] ; then ++ echo "/etc/hosts.augnew no longer links to ../other/attack" ++ exit 1 ++fi ++ ++if [ -s $ATTACK_FILE ]; then ++ echo "/other/attack now contains data, should be blank" ++ exit 1 ++fi +diff --git a/tests/test-put-symlink.sh b/tests/test-put-symlink.sh +index 6996b23..64749ea 100755 +--- a/tests/test-put-symlink.sh ++++ b/tests/test-put-symlink.sh +@@ -41,3 +41,8 @@ if [ "x$LINK" != "x../other/hosts" ] ; then + echo "/etc/hosts does not link to ../other/hosts" + exit 1 + fi ++ ++if ! grep myhost $REAL_HOSTS >/dev/null; then ++ echo "/other/hosts does not contain the modification" ++ exit 1 ++fi +diff --git a/tests/test-save-empty.sh b/tests/test-save-empty.sh +index 00741da..847fd69 100755 +--- a/tests/test-save-empty.sh ++++ b/tests/test-save-empty.sh +@@ -15,7 +15,7 @@ EOF + + expected_errors() { + cat <<EOF +-/augeas/files/etc/hosts/error = "open_augnew" ++/augeas/files/etc/hosts/error = "mk_augtemp" + /augeas/files/etc/hosts/error/message = "No such file or directory" + EOF + } +@@ -30,7 +30,8 @@ EXPECTED=$(expected_errors) + + if [ "$ACTUAL" != "$EXPECTED" ] + then +- echo "No error on missing /etc directory" ++ echo "No error on missing /etc directory:" ++ echo "$ACTUAL" + exit 1 + fi + +-- +1.8.0 + diff --git a/main/augeas/CVE-2012-0787.patch b/main/augeas/CVE-2012-0787.patch new file mode 100644 index 0000000000..31ae98eaed --- /dev/null +++ b/main/augeas/CVE-2012-0787.patch @@ -0,0 +1,363 @@ +From db42d52a8a1842726eb88e8da326ea37c2edaa27 Mon Sep 17 00:00:00 2001 +From: Dominic Cleal <dcleal@redhat.com> +Date: Sun, 25 Mar 2012 17:07:54 +0100 +Subject: [PATCH] Prevent cross-mountpoint attacks via .augsave during saving + +Previously Augeas would open PATH.augsave for writing if a rename from PATH to +PATH.augsave failed, then write the file contents in. Now if the rename fails, +it tries to unlink PATH.augsave and open it with O_EXCL first. + +Mountpoints remain permitted at either PATH or PATH.augnew provided +/augeas/save/copy_if_rename_fails exists. + +* src/transform.c (clone_file): + add argument to perform unlink and O_EXCL on destination filename after a + rename failure to prevent PATH.augsave being a mountpoint +* src/transform.c (transform_save, remove_file): + always try to unlink PATH.augsave if rename fails, only allowing PATH to be + a mountpoint; allow PATH or PATH.augnew to be mountpoints +* tests/ + test-put-mount: check PATH being a mountpoint is supported + test-put-mount-augnew.sh: check PATH.augnew being a mountpoint is supported + test-put-mount-augsave.sh: check unlink error when PATH.augsave is a mount + +Fixes BZ 772261 +(cherry picked from commit b8de6a8c5cfffb007149036ffa561ced4d11c462) +--- + src/transform.c | 40 ++++++++++++++++++++---- + tests/Makefile.am | 5 +-- + tests/test-put-mount-augnew.sh | 69 +++++++++++++++++++++++++++++++++++++++++ + tests/test-put-mount-augsave.sh | 62 ++++++++++++++++++++++++++++++++++++ + tests/test-put-mount.sh | 55 ++++++++++++++++++++++++++++++++ + 5 files changed, 223 insertions(+), 8 deletions(-) + create mode 100755 tests/test-put-mount-augnew.sh + create mode 100755 tests/test-put-mount-augsave.sh + create mode 100755 tests/test-put-mount.sh + +diff --git a/src/transform.c b/src/transform.c +index 0936eb7..00ca3ec 100644 +--- a/src/transform.c ++++ b/src/transform.c +@@ -27,6 +27,7 @@ + + #include <sys/types.h> + #include <sys/stat.h> ++#include <fcntl.h> + #include <unistd.h> + #include <selinux/selinux.h> + #include <stdbool.h> +@@ -784,14 +785,21 @@ static int transfer_file_attrs(FILE *from, FILE *to, + * means that FROM or TO is a bindmounted file), and COPY_IF_RENAME_FAILS + * is true, copy the contents of FROM into TO and delete FROM. + * ++ * If COPY_IF_RENAME_FAILS and UNLINK_IF_RENAME_FAILS are true, and the above ++ * copy mechanism is used, it will unlink the TO path and open with O_EXCL ++ * to ensure we only copy *from* a bind mount rather than into an attacker's ++ * mount placed at TO (e.g. for .augsave). ++ * + * Return 0 on success (either rename succeeded or we copied the contents + * over successfully), -1 on failure. + */ + static int clone_file(const char *from, const char *to, +- const char **err_status, int copy_if_rename_fails) { ++ const char **err_status, int copy_if_rename_fails, ++ int unlink_if_rename_fails) { + FILE *from_fp = NULL, *to_fp = NULL; + char buf[BUFSIZ]; + size_t len; ++ int to_fd = -1, to_oflags, r; + int result = -1; + + if (rename(from, to) == 0) +@@ -808,10 +816,23 @@ static int clone_file(const char *from, const char *to, + goto done; + } + +- if (!(to_fp = fopen(to, "w"))) { ++ if (unlink_if_rename_fails) { ++ r = unlink(to); ++ if (r < 0) { ++ *err_status = "clone_unlink_dst"; ++ goto done; ++ } ++ } ++ ++ to_oflags = unlink_if_rename_fails ? O_EXCL : O_TRUNC; ++ if ((to_fd = open(to, O_WRONLY|O_CREAT|to_oflags, S_IRUSR|S_IWUSR)) < 0) { + *err_status = "clone_open_dst"; + goto done; + } ++ if (!(to_fp = fdopen(to_fd, "w"))) { ++ *err_status = "clone_fdopen_dst"; ++ goto done; ++ } + + if (transfer_file_attrs(from_fp, to_fp, err_status) < 0) + goto done; +@@ -838,8 +859,15 @@ static int clone_file(const char *from, const char *to, + done: + if (from_fp != NULL) + fclose(from_fp); +- if (to_fp != NULL && fclose(to_fp) != 0) ++ if (to_fp != NULL) { ++ if (fclose(to_fp) != 0) { ++ *err_status = "clone_fclose_dst"; ++ result = -1; ++ } ++ } else if (to_fd >= 0 && close(to_fd) < 0) { ++ *err_status = "clone_close_dst"; + result = -1; ++ } + if (result != 0) + unlink(to); + if (result == 0) +@@ -1072,7 +1100,7 @@ int transform_save(struct augeas *aug, struct tree *xfm, + goto done; + } + +- r = clone_file(augorig_canon, augsave, &err_status, 1); ++ r = clone_file(augorig_canon, augsave, &err_status, 1, 1); + if (r != 0) { + dyn_err_status = strappend(err_status, "_augsave"); + goto done; +@@ -1080,7 +1108,7 @@ int transform_save(struct augeas *aug, struct tree *xfm, + } + } + +- r = clone_file(augtemp, augdest, &err_status, copy_if_rename_fails); ++ r = clone_file(augtemp, augdest, &err_status, copy_if_rename_fails, 0); + if (r != 0) { + dyn_err_status = strappend(err_status, "_augtemp"); + goto done; +@@ -1171,7 +1199,7 @@ int remove_file(struct augeas *aug, struct tree *tree) { + goto error; + } + +- r = clone_file(augorig_canon, augsave, &err_status, 1); ++ r = clone_file(augorig_canon, augsave, &err_status, 1, 1); + if (r != 0) { + dyn_err_status = strappend(err_status, "_augsave"); + goto error; +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 3af1a0e..69c3c89 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -152,8 +152,9 @@ check_SCRIPTS = \ + $(lens_tests) \ + test-get.sh test-augtool.sh \ + test-put-symlink.sh test-put-symlink-augnew.sh \ +- test-put-symlink-augsave.sh test-put-symlink-augtemp.sh test-save-empty.sh \ +- test-bug-1.sh test-idempotent.sh test-preserve.sh \ ++ test-put-symlink-augsave.sh test-put-symlink-augtemp.sh \ ++ test-put-mount.sh test-put-mount-augnew.sh test-put-mount-augsave.sh \ ++ test-save-empty.sh test-bug-1.sh test-idempotent.sh test-preserve.sh \ + test-events-saved.sh test-save-mode.sh test-unlink-error.sh \ + test-augtool-empty-line.sh test-augtool-modify-root.sh + +diff --git a/tests/test-put-mount-augnew.sh b/tests/test-put-mount-augnew.sh +new file mode 100755 +index 0000000..cb95bda +--- /dev/null ++++ b/tests/test-put-mount-augnew.sh +@@ -0,0 +1,69 @@ ++#! /bin/bash ++ ++# Test that we can write into a bind mount placed at PATH.augnew with the ++# copy_if_rename_fails flag. ++# This requires that EXDEV or EBUSY is returned from rename(2) to activate the ++# code path, so set up a bind mount on Linux. ++ ++if [ $UID -ne 0 -o "$(uname -s)" != "Linux" ]; then ++ echo "Test can only be run as root on Linux to create bind mounts" ++ exit 77 ++fi ++ ++ROOT=$abs_top_builddir/build/test-put-mount-augnew ++LENSES=$abs_top_srcdir/lenses ++ ++HOSTS=$ROOT/etc/hosts ++HOSTS_AUGNEW=${HOSTS}.augnew ++TARGET=$ROOT/other/real_hosts ++ ++rm -rf $ROOT ++mkdir -p $(dirname $HOSTS) ++mkdir -p $(dirname $TARGET) ++ ++echo 127.0.0.1 localhost > $HOSTS ++touch $TARGET $HOSTS_AUGNEW ++ ++mount --bind $TARGET $HOSTS_AUGNEW ++Exit() { ++ umount $HOSTS_AUGNEW ++ exit $1 ++} ++ ++HOSTS_SUM=$(sum $HOSTS) ++ ++augtool --nostdinc -I $LENSES -r $ROOT --new <<EOF ++set /augeas/save/copy_if_rename_fails 1 ++set /files/etc/hosts/1/alias myhost ++save ++print /augeas//error ++EOF ++ ++if [ ! -f $HOSTS ] ; then ++ echo "/etc/hosts is no longer a regular file" ++ Exit 1 ++fi ++if [ ! "x${HOSTS_SUM}" = "x$(sum $HOSTS)" ]; then ++ echo "/etc/hosts has changed" ++ Exit 1 ++fi ++if [ ! "x${HOSTS_SUM}" = "x$(sum $HOSTS)" ]; then ++ echo "/etc/hosts has changed" ++ Exit 1 ++fi ++ ++if [ ! -s $HOSTS_AUGNEW ]; then ++ echo "/etc/hosts.augnew is empty" ++ Exit 1 ++fi ++if [ ! -s $TARGET ]; then ++ echo "/other/real_hosts is empty" ++ Exit 1 ++fi ++ ++if ! grep myhost $TARGET >/dev/null; then ++ echo "/other/real_hosts does not contain the modification" ++ Exit 1 ++fi ++ ++Exit 0 +diff --git a/tests/test-put-mount-augsave.sh b/tests/test-put-mount-augsave.sh +new file mode 100755 +index 0000000..31fcfca +--- /dev/null ++++ b/tests/test-put-mount-augsave.sh +@@ -0,0 +1,62 @@ ++#! /bin/bash ++ ++# Test that we don't follow bind mounts when writing to .augsave. ++# This requires that EXDEV or EBUSY is returned from rename(2) to activate the ++# code path, so set up a bind mount on Linux. ++ ++if [ $UID -ne 0 -o "$(uname -s)" != "Linux" ]; then ++ echo "Test can only be run as root on Linux to create bind mounts" ++ exit 77 ++fi ++ ++actual() { ++ (augtool --nostdinc -I $LENSES -r $ROOT --backup | grep ^/augeas) <<EOF ++ set /augeas/save/copy_if_rename_fails 1 ++ set /files/etc/hosts/1/alias myhost ++ save ++ print /augeas//error ++EOF ++} ++ ++expected() { ++ cat <<EOF ++/augeas/files/etc/hosts/error = "clone_unlink_dst_augsave" ++/augeas/files/etc/hosts/error/message = "Device or resource busy" ++EOF ++} ++ ++ROOT=$abs_top_builddir/build/test-put-mount-augsave ++LENSES=$abs_top_srcdir/lenses ++ ++HOSTS=$ROOT/etc/hosts ++HOSTS_AUGSAVE=${HOSTS}.augsave ++ ++ATTACK_FILE=$ROOT/other/attack ++ ++rm -rf $ROOT ++mkdir -p $(dirname $HOSTS) ++mkdir -p $(dirname $ATTACK_FILE) ++ ++echo 127.0.0.1 localhost > $HOSTS ++touch $ATTACK_FILE $HOSTS_AUGSAVE ++ ++mount --bind $ATTACK_FILE $HOSTS_AUGSAVE ++Exit() { ++ umount $HOSTS_AUGSAVE ++ exit $1 ++} ++ ++ACTUAL=$(actual) ++EXPECTED=$(expected) ++if [ "$ACTUAL" != "$EXPECTED" ]; then ++ echo "No error when trying to unlink augsave (a bind mount):" ++ echo "$ACTUAL" ++ exit 1 ++fi ++ ++if [ -s $ATTACK_FILE ]; then ++ echo "/other/attack now contains data, should be blank" ++ Exit 1 ++fi ++ ++Exit 0 +diff --git a/tests/test-put-mount.sh b/tests/test-put-mount.sh +new file mode 100755 +index 0000000..210bc10 +--- /dev/null ++++ b/tests/test-put-mount.sh +@@ -0,0 +1,55 @@ ++#! /bin/bash ++ ++# Test that we can write into a bind mount with the copy_if_rename_fails flag. ++# This requires that EXDEV or EBUSY is returned from rename(2) to activate the ++# code path, so set up a bind mount on Linux. ++ ++if [ $UID -ne 0 -o "$(uname -s)" != "Linux" ]; then ++ echo "Test can only be run as root on Linux to create bind mounts" ++ exit 77 ++fi ++ ++ROOT=$abs_top_builddir/build/test-put-mount ++LENSES=$abs_top_srcdir/lenses ++ ++HOSTS=$ROOT/etc/hosts ++TARGET=$ROOT/other/real_hosts ++ ++rm -rf $ROOT ++mkdir -p $(dirname $HOSTS) ++mkdir -p $(dirname $TARGET) ++ ++echo 127.0.0.1 localhost > $TARGET ++touch $HOSTS ++ ++mount --bind $TARGET $HOSTS ++Exit() { ++ umount $HOSTS ++ exit $1 ++} ++ ++HOSTS_SUM=$(sum $HOSTS) ++ ++augtool --nostdinc -I $LENSES -r $ROOT <<EOF ++set /augeas/save/copy_if_rename_fails 1 ++set /files/etc/hosts/1/alias myhost ++save ++print /augeas//error ++EOF ++ ++if [ ! "x${HOSTS_SUM}" != "x$(sum $HOSTS)" ]; then ++ echo "/etc/hosts hasn't changed" ++ Exit 1 ++fi ++ ++if [ ! "x${HOSTS_SUM}" != "x$(sum $TARGET)" ]; then ++ echo "/other/real_hosts hasn't changed" ++ Exit 1 ++fi ++ ++if ! grep myhost $TARGET >/dev/null; then ++ echo "/other/real_hosts does not contain the modification" ++ Exit 1 ++fi ++ ++Exit 0 +-- +1.8.0 + |