summaryrefslogtreecommitdiff
blob: ddb36605f84d4da03b00000cdbd4daf4b9bbf29c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
From 3fac92713621f894d33a9f499bacb5b20f0d8ec3 Mon Sep 17 00:00:00 2001
From: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue, 20 Oct 2020 13:37:15 -0300
Subject: [PATCH 1/3] linux: Use getdents64 on non-LFS readdir

It is similar to what non-LFS getdents do (including overflow check).

Checked on x86_64-linux-gnu and i686-linux-gnu.
---
 sysdeps/unix/sysv/linux/readdir.c | 97 +++++++++++++++++++++++--------
 1 file changed, 73 insertions(+), 24 deletions(-)

diff --git a/sysdeps/unix/sysv/linux/readdir.c b/sysdeps/unix/sysv/linux/readdir.c
index 33bae4b57d..4e2214f21e 100644
--- a/sysdeps/unix/sysv/linux/readdir.c
+++ b/sysdeps/unix/sysv/linux/readdir.c
@@ -20,43 +20,92 @@
 
 #if !_DIRENT_MATCHES_DIRENT64
 #include <dirstream.h>
+#include <unistd.h>
+
+# ifndef DIRENT_SET_DP_INO
+#  define DIRENT_SET_DP_INO(dp, value) (dp)->d_ino = (value)
+# endif
 
 /* Read a directory entry from DIRP.  */
 struct dirent *
 __readdir_unlocked (DIR *dirp)
 {
-  struct dirent *dp;
   int saved_errno = errno;
 
-  if (dirp->offset >= dirp->size)
+  while (1)
     {
-      /* We've emptied out our buffer.  Refill it.  */
-
-      size_t maxread = dirp->allocation;
-      ssize_t bytes;
-
-      bytes = __getdents (dirp->fd, dirp->data, maxread);
-      if (bytes <= 0)
+      if (dirp->offset >= dirp->size)
 	{
-	  /* Linux may fail with ENOENT on some file systems if the
-	     directory inode is marked as dead (deleted).  POSIX
-	     treats this as a regular end-of-directory condition, so
-	     do not set errno in that case, to indicate success.  */
-	  if (bytes == 0 || errno == ENOENT)
-	    __set_errno (saved_errno);
+	  ssize_t bytes = __getdents64 (dirp->fd, dirp->data,
+					dirp->allocation);
+	  if (bytes <= 0)
+	    {
+	      /* Linux may fail with ENOENT on some file systems if the
+		 directory inode is marked as dead (deleted).  POSIX
+		 treats this as a regular end-of-directory condition, so
+		 do not set errno in that case, to indicate success.  */
+	      if (bytes < 0 && errno == ENOENT)
+		__set_errno (saved_errno);
+	      return NULL;
+	    }
+	  dirp->size = bytes;
+
+ 	  /* Reset the offset into the buffer.  */
+	  dirp->offset = 0;
+ 	}
+
+    /* These two pointers might alias the same memory buffer.  Standard C
+       requires that we always use the same type for them, so we must use the
+       union type.  */
+      union
+      {
+	struct dirent64 dp64;
+	struct dirent dp;
+	char *b;
+      } *inp, *outp;
+      inp = (void*) &dirp->data[dirp->offset];
+      outp = (void*) &dirp->data[dirp->offset];
+
+      const size_t size_diff = offsetof (struct dirent64, d_name)
+	- offsetof (struct dirent, d_name);
+
+      /* Since inp->dp64.d_reclen is already aligned for the kernel structure
+	 this may compute a value that is bigger than necessary.  */
+      size_t old_reclen = inp->dp64.d_reclen;
+      size_t new_reclen = ALIGN_UP (old_reclen - size_diff,
+				    _Alignof (struct dirent));
+
+      if (!in_ino_t_range (inp->dp64.d_ino)
+	  || !in_off_t_range (inp->dp64.d_off))
+	{
+	  /* Overflow.  If there was at least one entry before this one,
+	     return them without error, otherwise signal overflow.  */
+	  if (dirp->offset != 0)
+	    {
+	      __lseek64 (dirp->fd, dirp->offset, SEEK_SET);
+	      outp = (void*)(outp->b - dirp->data);
+	      return &outp->dp;
+	    }
+	  __set_errno (EOVERFLOW);
 	  return NULL;
 	}
-      dirp->size = (size_t) bytes;
 
-      /* Reset the offset into the buffer.  */
-      dirp->offset = 0;
+      /* Copy the data from INP and access only OUTP.  */
+      const uint64_t d_ino = inp->dp64.d_ino;
+      const int64_t d_off = inp->dp64.d_off;
+      const uint8_t d_type = inp->dp64.d_type;
+      outp->dp.d_ino = d_ino;
+      outp->dp.d_off = d_off;
+      outp->dp.d_reclen = new_reclen;
+      outp->dp.d_type = d_type;
+      memmove (outp->dp.d_name, inp->dp64.d_name,
+	       old_reclen - offsetof (struct dirent64, d_name));
+
+      dirp->filepos = d_off;
+      dirp->offset += old_reclen;
+
+      return &outp->dp;
     }
-
-  dp = (struct dirent *) &dirp->data[dirp->offset];
-  dirp->offset += dp->d_reclen;
-  dirp->filepos = dp->d_off;
-
-  return dp;
 }
 
 struct dirent *
-- 
2.41.0