File: Make-crontab-SGID-crontab.patch

package info (click to toggle)
cron 3.0pl1-197
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 3,816 kB
  • sloc: ansic: 54,879; xml: 1,600; perl: 733; sh: 463; makefile: 446; python: 43
file content (239 lines) | stat: -rw-r--r-- 6,516 bytes parent folder | download | duplicates (2)
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
From: Christian Kastner <ckk@kvr.at>
Date: Sun, 10 Jan 2016 01:03:51 +0100
Subject: Make crontab SGID crontab

Improve security by making crontab(1) SGID crontab instead of SUID root.

Specifically:
  * Drop the call to set_cron_uid(), which is just a wrapper to seteuid()
  * setgid() to the real group ID before exec'ing the editor
  * Create spool dir with the expected permissions

Nevertheless, Tomi Miettinen's advice in #18333 of not SETing at all, and
using sockets instead should be taken into consideration.

Bug-Debian: https://bugs.debian.org/18333
Forwarded: no
Last-Update: 2016-01-10
---
 cron.8      |  2 ++
 crontab.1   |  5 +++++
 crontab.c   | 32 ++++++++++++++------------------
 misc.c      | 41 +++++++++++++++++++++++++++++++++++++----
 pathnames.h | 20 ++++++++++++++++++++
 5 files changed, 78 insertions(+), 22 deletions(-)

diff --git a/cron.8 b/cron.8
index 86831e7..2ba9b34 100644
--- a/cron.8
+++ b/cron.8
@@ -181,6 +181,8 @@ auditlog support,
 .IP \(em
 DST and other time-related changes/fixes,
 .IP \(em
+SGID crontab(1) instead of SUID root,
+.IP \(em
 Debian-specific file locations and commands,
 .IP \(em
 Debian-specific configuration (/etc/default/cron),
diff --git a/crontab.1 b/crontab.1
index 2741db6..b349099 100644
--- a/crontab.1
+++ b/crontab.1
@@ -118,6 +118,11 @@ There is one file for each user's crontab under the /var/spool/cron/crontabs
 directory.  Users are not allowed to edit the files under that directory
 directly to ensure that only users allowed by the system to run periodic tasks
 can add them, and only syntactically correct crontabs will be written there.
+This is enforced by having the directory writable only by the
+.I crontab
+group and configuring
+.I crontab
+command with the setgid bid set for that specific group.
 .SH STANDARDS
 The
 .I crontab
diff --git a/crontab.c b/crontab.c
index 8cfbeb6..c81bc87 100644
--- a/crontab.c
+++ b/crontab.c
@@ -110,7 +110,6 @@ main(argc, argv)
 		argv[1] = "-";
 	}
 	parse_args(argc, argv);		/* sets many globals, opens a file */
-	set_cron_uid();
 	set_cron_cwd();
 	if (!allowed(User)) {
 		if (getuid() != 0) {
@@ -373,12 +372,6 @@ create_tmp_crontab()
 		return -1;
 	}
 
-	if (chown(Directory, getuid(), getgid()) < 0) {
-		perror(Directory);
-		Directory[0] = '\0';
-		return -1;
-	}
-
 	/* Now create the actual temporary crontab file */
 	if (snprintf(Filename, MAX_FNAME, "%s/crontab", Directory) >= MAX_FNAME) {
 		fprintf(stderr, "Temporary filename too long - aborting\n");
@@ -413,7 +406,7 @@ open_tmp_crontab(fsbuf)
 		perror("fstat");
 		return -1;
 	}
-	if (statbuf.st_uid != getuid() || statbuf.st_gid != getgid()) {
+	if (statbuf.st_uid != getuid()) {
 		fprintf(stderr, "Temporary crontab no longer owned by you.\n");
 		return -1;;
 	}
@@ -582,6 +575,10 @@ again: /* Loop point for retrying edit after error */
 		goto fatal;
 	case 0:
 		/* child */
+		if (setgid(getgid()) < 0) {
+			perror("setgid(getgid())");
+			exit(ERROR_EXIT);
+		}
 		if (setuid(getuid()) < 0) {
 			perror("setuid(getuid())");
 			exit(ERROR_EXIT);
@@ -816,16 +813,6 @@ replace_cmd() {
 		return (-1);
 	}
 
-#ifdef HAS_FCHOWN
-	if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
-#else
-	if (chown(tn, ROOT_UID, -1) < OK)
-#endif
-	{
-		perror("chown");
-		fclose(tmp);  unlink(tn);
-		return (-2);
-	}
 
 #ifdef HAS_FCHMOD
 	if (fchmod(fileno(tmp), 0600) < OK)
@@ -844,6 +831,15 @@ replace_cmd() {
 		return (-2);
 	}
 
+	/* Root on behalf of another user must set file owner to that user */
+	if (getuid() == ROOT_UID && strcmp(User, RealUser) != 0) {
+		if (chown(tn, pw->pw_uid, -1) != 0) {
+			perror("chown");
+			unlink(tn);
+			return -2;
+		}
+	}
+
 	(void) snprintf(n, sizeof(n), CRON_TAB(User));
 	if (rename(tn, n)) {
 		fprintf(stderr, "%s: %s: rename: %s\n",
diff --git a/misc.c b/misc.c
index ac0cfb8..43174c2 100644
--- a/misc.c
+++ b/misc.c
@@ -35,6 +35,7 @@ static char rcsid[] = "$Id: misc.c,v 2.9 1994/01/15 20:43:43 vixie Exp $";
 #include <errno.h>
 #include <string.h>
 #include <fcntl.h>
+#include <grp.h>
 #ifdef WITH_AUDIT
 #include <libaudit.h>
 #endif
@@ -198,18 +199,28 @@ void
 set_cron_cwd()
 {
 	struct stat	sb;
+	mode_t		um;
+	struct group	*gr;
 
 	/* first check for CRONDIR ("/var/cron" or some such)
 	 */
 	if (stat(CRONDIR, &sb) < OK && errno == ENOENT) {
 		perror(CRONDIR);
-		if (OK == mkdir(CRONDIR, 0700)) {
+
+		/* crontab(1) running SGID crontab shouldn't attempt to create
+		 * directories */
+		if (getuid() != 0)
+			exit(ERROR_EXIT);
+
+		um = umask(000);
+		if (OK == mkdir(CRONDIR, CRONDIR_MODE)) {
 			fprintf(stderr, "%s: created\n", CRONDIR);
 			stat(CRONDIR, &sb);
 		} else {
 			fprintf(stderr, "%s: mkdir: %s\n", CRONDIR, strerror(errno));
 			exit(ERROR_EXIT);
 		}
+		(void) umask(um);
 	}
 	if (!(sb.st_mode & S_IFDIR)) {
 		fprintf(stderr, "'%s' is not a directory, bailing out.\n",
@@ -225,11 +236,33 @@ set_cron_cwd()
 	 */
 	if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
 		perror(SPOOL_DIR);
-		if (OK == mkdir(SPOOL_DIR, 0700)) {
+
+		/* crontab(1) running SGID crontab shouldn't attempt to create
+		 * directories */
+		if (getuid() != 0 )
+			exit(ERROR_EXIT);
+
+		um = umask(000);
+		if (OK == mkdir(SPOOL_DIR, SPOOL_DIR_MODE)) {
 			fprintf(stderr, "%s: created\n", SPOOL_DIR);
-			stat(SPOOL_DIR, &sb);
 		} else {
-			fprintf(stderr, "%s: mkdir: %s\n", SPOOL_DIR, strerror(errno));
+			fprintf(stderr, "%s: mkdir: %s\n", SPOOL_DIR,
+				strerror(errno));
+			exit(ERROR_EXIT);
+		}
+		(void) umask(um);
+
+		if (!(gr = getgrnam(SPOOL_DIR_GROUP))) {
+			fprintf(stderr, "%s: getgrnam: %s\n", SPOOL_DIR,
+				strerror(errno));
+			exit(ERROR_EXIT);
+		}
+		if (OK == chown(SPOOL_DIR, -1, gr->gr_gid)) {
+			fprintf(stderr, "%s: chowned\n", SPOOL_DIR);
+				stat(SPOOL_DIR, &sb);
+		} else {
+			fprintf(stderr, "%s: chown: %s\n", SPOOL_DIR,
+			strerror(errno));
 			exit(ERROR_EXIT);
 		}
 	}
diff --git a/pathnames.h b/pathnames.h
index 5170e99..669d33f 100644
--- a/pathnames.h
+++ b/pathnames.h
@@ -81,3 +81,23 @@
 #ifndef REBOOT_FILE
 # define REBOOT_FILE "/run/crond.reboot"
 #endif
+
+
+#ifndef CRONDIR_MODE
+			/* Create mode for CRONDIR; must be in sync with
+			 * packaging
+			 */
+#define CRONDIR_MODE 0755
+#endif
+#ifndef SPOOL_DIR_MODE
+			/* Create mode for SPOOL_DIR; must be in sync with
+			 * packaging
+			 */
+#define SPOOL_DIR_MODE 01730
+#endif
+#ifndef SPOOL_DIR_GROUP
+			/* Chown SPOOL_DIR to this group (needed by Debian's
+			 * SGID crontab feature)
+			 */
+#define SPOOL_DIR_GROUP "crontab"
+#endif