summary refs log tree commit diff
path: root/gnu/packages/patches/sudo-CVE-2015-5602.patch
blob: 36c90fbee7161c1611f80c8c12d4d1d29c455b78 (plain) (blame)
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
Based on the patch from https://www.sudo.ws/repos/sudo/raw-rev/c2e36a80a279
Backported to 1.8.15 by Mark H Weaver <mhw@netris.org>

# HG changeset patch
# User Todd C. Miller <Todd.Miller@courtesan.com>
# Date 1452475889 25200
# Node ID c2e36a80a27927c32cba55afae78b8dc830cddc3
# Parent  94ffd6b18431fa4b9ed0a0c3f0b7b9582a4f6bde
Rewritten sudoedit_checkdir support that checks all the dirs in the
path and refuses to follow symlinks in writable directories.
This is a better fix for CVE-2015-5602.
Adapted from a diff by Ben Hutchings.  Bug #707

diff -r 94ffd6b18431 -r c2e36a80a279 doc/CONTRIBUTORS
--- a/doc/CONTRIBUTORS	Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/CONTRIBUTORS	Sun Jan 10 18:31:29 2016 -0700
@@ -58,6 +58,7 @@
     Holloway, Nick
     Hoover, Adam
     Hunter, Michael T.
+    Hutchings, Ben
     Irrgang, Eric
     Jackson, Brian
     Jackson, John R.
diff -r 94ffd6b18431 -r c2e36a80a279 doc/UPGRADE
--- a/doc/UPGRADE	Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/UPGRADE	Sun Jan 10 18:31:29 2016 -0700
@@ -1,6 +1,15 @@
 Notes on upgrading from an older release
 ========================================
 
+o Upgrading from a version prior to the post-1.8.15 fix for CVE-2015-5602.
+
+    The meaning of the sudoedit_checkdir sudoers option has changed.
+    Previously, it would only check the parent directory
+    of the file to be edited.  After the CVE fix, all directories
+    in the path to be edited are checked and sudoedit will refuse
+    to follow a symbolic link in a directory that is writable by
+    the invoking user.
+
 o Upgrading from a version prior to 1.8.15:
 
     Prior to version 1.8.15, when env_reset was enabled (the default)
diff -r 94ffd6b18431 -r c2e36a80a279 doc/sudoers.cat
--- a/doc/sudoers.cat	Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/sudoers.cat	Sun Jan 10 18:31:29 2016 -0700
@@ -1275,12 +1275,15 @@
                        system call.  This flag is _o_f_f by default.
 
      sudoedit_checkdir
-                       If set, ssuuddooeeddiitt will refuse to edit files located in a
-                       directory that is writable by the invoking user unless
-                       it is run by root.  On many systems, this option
-                       requires that the parent directory of the file to be
-                       edited be readable by the target user.  This flag is
-                       _o_f_f by default.
+                       If set, ssuuddooeeddiitt will check directories in the path to
+                       be edited for writability by the invoking user.
+                       Symbolic links will not be followed in writable
+                       directories and ssuuddooeeddiitt will also refuse to edit a
+                       file located in a writable directory.  Theses
+                       restrictions are not enforced when ssuuddooeeddiitt is invoked
+                       as root.  On many systems, this option requires that
+                       all directories in the path to be edited be readable by
+                       the target user.  This flag is _o_f_f by default.
 
      sudoedit_follow   By default, ssuuddooeeddiitt will not follow symbolic links
                        when opening files.  The _s_u_d_o_e_d_i_t___f_o_l_l_o_w option can be
diff -r 94ffd6b18431 -r c2e36a80a279 doc/sudoers.man.in
--- a/doc/sudoers.man.in	Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/sudoers.man.in	Sun Jan 10 18:31:29 2016 -0700
@@ -2715,10 +2715,16 @@
 .br
 If set,
 \fBsudoedit\fR
-will refuse to edit files located in a directory that is writable
-by the invoking user unless it is run by root.
-On many systems, this option requires that the parent directory
-of the file to be edited be readable by the target user.
+will check directories in the path to be edited for writability
+by the invoking user.
+Symbolic links will not be followed in writable directories and
+\fBsudoedit\fR
+will also refuse to edit a file located in a writable directory.
+Theses restrictions are not enforced when
+\fBsudoedit\fR
+is invoked as root.
+On many systems, this option requires that all directories
+in the path to be edited be readable by the target user.
 This flag is
 \fIoff\fR
 by default.
diff -r 94ffd6b18431 -r c2e36a80a279 doc/sudoers.mdoc.in
--- a/doc/sudoers.mdoc.in	Mon Jan 04 10:47:11 2016 -0700
+++ b/doc/sudoers.mdoc.in	Sun Jan 10 18:31:29 2016 -0700
@@ -2549,10 +2549,16 @@
 .It sudoedit_checkdir
 If set,
 .Nm sudoedit
-will refuse to edit files located in a directory that is writable
-by the invoking user unless it is run by root.
-On many systems, this option requires that the parent directory
-of the file to be edited be readable by the target user.
+will check directories in the path to be edited for writability
+by the invoking user.
+Symbolic links will not be followed in writable directories and
+.Nm sudoedit
+will also refuse to edit a file located in a writable directory.
+Theses restrictions are not enforced when
+.Nm sudoedit
+is invoked as root.
+On many systems, this option requires that all directories
+in the path to be edited be readable by the target user.
 This flag is
 .Em off
 by default.
diff -r 94ffd6b18431 -r c2e36a80a279 include/sudo_compat.h
--- a/include/sudo_compat.h	Mon Jan 04 10:47:11 2016 -0700
+++ b/include/sudo_compat.h	Sun Jan 10 18:31:29 2016 -0700
@@ -182,6 +182,8 @@
 # ifndef UTIME_NOW
 #  define UTIME_NOW	-2L
 # endif
+#endif
+#if !defined(HAVE_OPENAT) || (!defined(HAVE_FUTIMENS) && !defined(HAVE_UTIMENSAT))
 # ifndef AT_FDCWD
 #  define AT_FDCWD	-100
 # endif
diff -r 94ffd6b18431 -r c2e36a80a279 src/sudo_edit.c
--- a/src/sudo_edit.c	Mon Jan 04 10:47:11 2016 -0700
+++ b/src/sudo_edit.c	Sun Jan 10 18:31:29 2016 -0700
@@ -179,13 +179,15 @@
 }
 
 #ifndef HAVE_OPENAT
-/* This does not support AT_FDCWD... */
 static int
 sudo_openat(int dfd, const char *path, int flags, mode_t mode)
 {
     int fd, odfd;
     debug_decl(sudo_openat, SUDO_DEBUG_EDIT)
 
+    if (dfd == AT_FDCWD)
+	debug_return_int(open(path, flags, mode));
+
     /* Save cwd */
     if ((odfd = open(".", O_RDONLY)) == -1)
 	debug_return_int(-1);
@@ -207,6 +209,64 @@
 #define openat sudo_openat
 #endif /* HAVE_OPENAT */
 
+#ifdef O_NOFOLLOW
+static int
+sudo_edit_openat_nofollow(int dfd, char *path, int oflags, mode_t mode)
+{
+    debug_decl(sudo_edit_open_nofollow, SUDO_DEBUG_EDIT)
+
+    debug_return_int(openat(dfd, path, oflags|O_NOFOLLOW, mode));
+}
+#else
+/*
+ * Returns true if fd and path don't match or path is a symlink.
+ * Used on older systems without O_NOFOLLOW.
+ */
+static bool
+sudo_edit_is_symlink(int fd, char *path)
+{
+    struct stat sb1, sb2;
+    debug_decl(sudo_edit_is_symlink, SUDO_DEBUG_EDIT)
+
+    /*
+     * Treat [fl]stat() failure like there was a symlink.
+     */
+    if (fstat(fd, &sb1) == -1 || lstat(path, &sb2) == -1)
+	debug_return_bool(true);
+
+    /*
+     * Make sure we did not open a link and that what we opened
+     * matches what is currently on the file system.
+     */
+    if (S_ISLNK(sb2.st_mode) ||
+	sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
+	debug_return_bool(true);
+    }
+
+    debug_return_bool(false);
+}
+
+static int
+sudo_edit_openat_nofollow(char *path, int oflags, mode_t mode)
+{
+    struct stat sb1, sb2;
+    int fd;
+    debug_decl(sudo_edit_openat_nofollow, SUDO_DEBUG_EDIT)
+
+    fd = openat(dfd, path, oflags, mode);
+    if (fd == -1)
+	debug_return_int(-1);
+
+    if (sudo_edit_is_symlink(fd, path)) {
+	close(fd);
+	fd = -1;
+	errno = ELOOP;
+    }
+
+    debug_return_int(fd);
+}
+#endif /* O_NOFOLLOW */
+
 /*
  * Returns true if the directory described by sb is writable
  * by the user.  We treat directories with the sticky bit as
@@ -245,49 +305,94 @@
     debug_return_bool(false);
 }
 
+/*
+ * Directory open flags for use with openat(2) and fstat(2).
+ * Use O_PATH and O_DIRECTORY where possible.
+ */
+#if defined(O_PATH) && defined(O_DIRECTORY)
+# define DIR_OPEN_FLAGS	(O_PATH|O_DIRECTORY)
+#elif defined(O_PATH) && !defined(O_DIRECTORY)
+# define DIR_OPEN_FLAGS	O_PATH
+#elif !defined(O_PATH) && defined(O_DIRECTORY)
+# define DIR_OPEN_FLAGS	(O_RDONLY|O_DIRECTORY)
+#else
+# define DIR_OPEN_FLAGS	(O_RDONLY|O_NONBLOCK)
+#endif
+
 static int
 sudo_edit_open_nonwritable(char *path, int oflags, mode_t mode)
 {
-    char *base, *dir;
+    int dfd, fd, dflags = DIR_OPEN_FLAGS;
+#if defined(__linux__) && defined(O_PATH)
+    char *opath = path;
+#endif
+    bool is_writable;
     struct stat sb;
-    int dfd, fd;
     debug_decl(sudo_edit_open_nonwritable, SUDO_DEBUG_EDIT)
 
-    base = strrchr(path, '/');
-    if (base != NULL) {
-	*base++ = '\0';
-	dir = path;
+#if defined(__linux__) && defined(O_PATH)
+restart:
+#endif
+    if (path[0] == '/') {
+	dfd = open("/", dflags);
+	path++;
     } else {
-	base = path;
-	dir = ".";
+	dfd = open(".", dflags);
+	if (path[0] == '.' && path[1] == '/')
+	    path += 2;
     }
-#ifdef O_PATH
-    if ((dfd = open(dir, O_PATH)) != -1) {
-	/* Linux kernels < 3.6 can't do fstat on O_PATH fds. */
-	if (fstat(dfd, &sb) == -1) {
-	    close(dfd);
-	    dfd = open(dir, O_RDONLY);
-	    if (fstat(dfd, &sb) == -1) {
-		close(dfd);
-		dfd = -1;
-	    }
-	}
-    }
-#else
-    if ((dfd = open(dir, O_RDONLY)) != -1) {
-	if (fstat(dfd, &sb) == -1) {
-	    close(dfd);
-	    dfd = -1;
-	}
-    }
-#endif
-    if (base != path)
-	base[-1] = '/';			/* restore path */
     if (dfd == -1)
 	debug_return_int(-1);
 
-    if (dir_is_writable(&sb, user_details.uid, user_details.gid,
-	user_details.ngroups, user_details.groups)) {
+    for (;;) {
+	char *slash;
+	int subdfd;
+
+	/*
+	 * Look up one component at a time, avoiding symbolic links in
+	 * writable directories.
+	 */
+	if (fstat(dfd, &sb) == -1) {
+	    close(dfd);
+#if defined(__linux__) && defined(O_PATH)
+	    /* Linux prior to 3.6 can't fstat an O_PATH fd */
+	    if (ISSET(dflags, O_PATH)) {
+		CLR(dflags, O_PATH);
+		path = opath;
+		goto restart;
+	    }
+#endif
+	    debug_return_int(-1);
+	}
+#ifndef O_DIRECTORY
+	if (!S_ISDIR(sb.st_mode)) {
+	    close(dfd);
+	    errno = ENOTDIR;
+	    debug_return_int(-1);
+	}
+#endif
+	is_writable = dir_is_writable(&sb, user_details.uid, user_details.gid,
+	    user_details.ngroups, user_details.groups);
+
+	while (path[0] == '/')
+	    path++;
+	slash = strchr(path, '/');
+	if (slash == NULL)
+	    break;
+	*slash = '\0';
+	if (is_writable)
+	    subdfd = sudo_edit_openat_nofollow(dfd, path, dflags, 0);
+	else
+	    subdfd = openat(dfd, path, dflags, 0);
+	*slash = '/';			/* restore path */
+	close(dfd);
+	if (subdfd == -1)
+	    debug_return_int(-1);
+	path = slash + 1;
+	dfd = subdfd;
+    }
+
+    if (is_writable) {
 	close(dfd);
 	errno = EISDIR;
 	debug_return_int(-1);
@@ -332,27 +437,10 @@
     if (!ISSET(oflags, O_NONBLOCK))
 	(void) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NONBLOCK);
 
-    /*
-     * Treat [fl]stat() failure like an open() failure.
-     */
-    if (fstat(fd, &sb1) == -1 || lstat(path, &sb2) == -1) {
-	const int serrno = errno;
+    if (!ISSET(sflags, CD_SUDOEDIT_FOLLOW) && sudo_edit_is_symlink(fd, path)) {
 	close(fd);
-	errno = serrno;
-	debug_return_int(-1);
-    }
-
-    /*
-     * Make sure we did not open a link and that what we opened
-     * matches what is currently on the file system.
-     */
-    if (!ISSET(sflags, CD_SUDOEDIT_FOLLOW)) {
-	if (S_ISLNK(sb2.st_mode) ||
-	    sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
-	    close(fd);
-	    errno = ELOOP;
-	    debug_return_int(-1);
-	}
+	fd = -1;
+	errno = ELOOP;
     }
 
     debug_return_int(fd);