about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/release-notes.xml10
-rw-r--r--src/libstore/local-store.cc32
2 files changed, 36 insertions, 6 deletions
diff --git a/doc/manual/release-notes.xml b/doc/manual/release-notes.xml
index 9cbd1fbb869d..513e4f03bc78 100644
--- a/doc/manual/release-notes.xml
+++ b/doc/manual/release-notes.xml
@@ -8,6 +8,16 @@
 
 <!--==================================================================-->
 
+<section xml:id="ssec-relnotes-1.5"><title>Release 1.5 (February 27, 2013)</title>
+
+<para>This is a brown paper bag release to fix a regression introduced
+by the hard link security fix in 1.4.</para>
+
+</section>
+
+
+<!--==================================================================-->
+
 <section xml:id="ssec-relnotes-1.4"><title>Release 1.4 (February 26, 2013)</title>
 
 <para>This release fixes a security bug in multi-user operation.  It
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index ff6b6c71ce0c..2c439a66d647 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -509,7 +509,11 @@ void canonicaliseTimestampAndPermissions(const Path & path)
 }
 
 
-static void canonicalisePathMetaData_(const Path & path, uid_t fromUid)
+typedef std::pair<dev_t, ino_t> Inode;
+typedef set<Inode> InodesSeen;
+
+
+static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSeen & inodesSeen)
 {
     checkInterrupt();
 
@@ -521,10 +525,24 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid)
        has already been checked in dumpPath(). */
     assert(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode));
 
-    canonicaliseTimestampAndPermissions(path, st);
+    /* Fail if the file is not owned by the build user.  This prevents
+       us from messing up the ownership/permissions of files
+       hard-linked into the output (e.g. "ln /etc/shadow $out/foo").
+       However, ignore files that we chown'ed ourselves previously to
+       ensure that we don't fail on hard links within the same build
+       (i.e. "touch $out/foo; ln $out/foo $out/bar"). */
+    if (fromUid != (uid_t) -1 && st.st_uid != fromUid) {
+        assert(!S_ISDIR(st.st_mode));
+        if (inodesSeen.find(Inode(st.st_dev, st.st_ino)) == inodesSeen.end())
+            throw BuildError(format("invalid ownership on file `%1%'") % path);
+        mode_t mode = st.st_mode & ~S_IFMT;
+        assert(st.st_uid == geteuid() && (mode == 0444 || mode == 0555) && st.st_mtime == mtimeStore);
+        return;
+    }
+
+    inodesSeen.insert(Inode(st.st_dev, st.st_ino));
 
-    if (fromUid != (uid_t) -1 && st.st_uid != fromUid)
-        throw BuildError(format("invalid ownership on file `%1%'") % path);
+    canonicaliseTimestampAndPermissions(path, st);
 
     /* Change ownership to the current uid.  If it's a symlink, use
        lchown if available, otherwise don't bother.  Wrong ownership
@@ -547,14 +565,16 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid)
     if (S_ISDIR(st.st_mode)) {
         Strings names = readDirectory(path);
         foreach (Strings::iterator, i, names)
-            canonicalisePathMetaData_(path + "/" + *i, fromUid);
+            canonicalisePathMetaData_(path + "/" + *i, fromUid, inodesSeen);
     }
 }
 
 
 void canonicalisePathMetaData(const Path & path, uid_t fromUid)
 {
-    canonicalisePathMetaData_(path, fromUid);
+    InodesSeen inodesSeen;
+
+    canonicalisePathMetaData_(path, fromUid, inodesSeen);
 
     /* On platforms that don't have lchown(), the top-level path can't
        be a symlink, since we can't change its ownership. */