[Toybox] [PATCH] cpio: fix misaligned header if readlink() returns unexpected value

Yi-Yo Chiang yochiang at google.com
Sun Feb 7 08:10:12 PST 2021


If file type is symlink and readlink() fails or returns unexpected link
size, then the file body wouldn't be written, resulting in a misaligned
archive.
As explained in the comments, there are some cases where the link size
returned by lstat() and readlink() may be different.
To accommodate this, we readlink() and update the value of st_size
before writing the file header.
This may happen on Android if the userdata filesystem is encrypted.

---
 tests/cpio.test   |  2 +-
 toys/posix/cpio.c | 24 ++++++++++++++++++++----
 2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/tests/cpio.test b/tests/cpio.test
index 11b3a5bf..a12edd09 100755
--- a/tests/cpio.test
+++ b/tests/cpio.test
@@ -39,7 +39,7 @@ rm a bb ccc dddd
 
 # archive dangling symlinks and empty files even if we cannot open them
 touch a; chmod a-rwx a; ln -s a/cant b
-toyonly testing "archives unreadable empty files" "cpio -o -H newc|cpio -it" "a\nb\n" "" "a\nb\n"
+toyonly testing "archives unreadable empty files" "cpio -o -H newc|cpio -it" "a\nb\na\n" "" "a\nb\na\n"
 chmod u+rw a; rm -f a b
 
 
diff --git a/toys/posix/cpio.c b/toys/posix/cpio.c
index 09c99ae1..200fb365 100644
--- a/toys/posix/cpio.c
+++ b/toys/posix/cpio.c
@@ -230,6 +230,7 @@ void cpio_main(void)
       unsigned nlen, error = 0, zero = 0;
       int len, fd = -1;
       ssize_t llen;
+      char *lnkbuf = NULL;
 
       len = getline(&name, &size, stdin);
       if (len<1) break;
@@ -242,6 +243,22 @@ void cpio_main(void)
         continue;
       }
 
+      // It is possible that readlink() may fail or the actual link size is
+      // different from that indicated by lstat(). This may be due to a race
+      // condition (the link is removed/changed between the call to lstat() and
+      // readlink()), or may happen on encrypted filesystem where lstat()
+      // returns the size of encrypted link and readlink() returns the size of
+      // decrypted link. Thus we first readlink() and update the st_size, bail
+      // out early if readlink() fails, then write the file header and the link.
+      if (S_ISLNK(st.st_mode)) {
+        lnkbuf = xreadlink(name);
+        if (!lnkbuf) {
+          perror_msg("readlink '%s'", name);
+          continue;
+        }
+        st.st_size = strlen(lnkbuf);
+      }
+
       if (FLAG(no_preserve_owner)) st.st_uid = st.st_gid = 0;
       if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) st.st_size = 0;
       if (st.st_size >> 32) perror_msg("skipping >2G file '%s'", name);
@@ -262,9 +279,7 @@ void cpio_main(void)
         // Write out body for symlink or regular file
         llen = st.st_size;
         if (S_ISLNK(st.st_mode)) {
-          if (readlink(name, toybuf, sizeof(toybuf)-1) == llen)
-            xwrite(afd, toybuf, llen);
-          else perror_msg("readlink '%s'", name);
+          xwrite(afd, lnkbuf, llen);
         } else while (llen) {
           nlen = llen > sizeof(toybuf) ? sizeof(toybuf) : llen;
           llen -= nlen;
@@ -276,7 +291,8 @@ void cpio_main(void)
         llen = st.st_size & 3;
         if (llen) xwrite(afd, &zero, 4-llen);
       }
-      close(fd);
+      if (lnkbuf) free(lnkbuf);
+      if (fd != -1) close(fd);
     }
     free(name);
 
-- 
2.30.0.478.g8a0d178c01-goog



More information about the Toybox mailing list