File logrotate-3.7.9-su.diff of Package logrotate.openSUSE_11.4_Update
From 2ceb9af5b48bacd8c7327a8b9b1c42848c24cdd9 Mon Sep 17 00:00:00 2001
From: jkaluza <jkaluza@ec1272ba-9ed1-42ef-8245-99669996828e>
Date: Wed, 9 Mar 2011 09:41:46 +0000
Subject: [PATCH logrotate] Backport svn rev 317, 358-360 (bnc#677335)
- don't accept service owned log directories anymore
- don't run external programs with uid != euid
- switch euid back to root before running external scripts
- run compressors and shred as unprivileged user
- add O_NOFOLLOW when opening files as safeguard against symlink tricks
- add "su" config option
---
config.c | 45 ++++++++++++++++++
logrotate.8 | 5 ++
logrotate.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
logrotate.h | 3 +
4 files changed, 183 insertions(+), 13 deletions(-)
diff --git a/config.c b/config.c
index 652cbe9..f2ad04e 100644
--- a/config.c
+++ b/config.c
@@ -440,6 +440,8 @@ int readAllConfigPaths(const char **paths)
.createMode = NO_MODE,
.createUid = NO_UID,
.createGid = NO_GID,
+ .suUid = 0,
+ .suGid = 0,
.compress_options_list = NULL,
.compress_options_count = 0
};
@@ -752,6 +754,49 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
newlog->flags &= ~LOG_FLAG_MAILFIRST;
*endtag = oldchar, start = endtag;
+ } else if (!strcmp(start, "su")) {
+ *endtag = oldchar, start = endtag;
+
+ endtag = start;
+ while (*endtag != '\n')
+ endtag++;
+ while (isspace(*endtag))
+ endtag--;
+ endtag++;
+ oldchar = *endtag, *endtag = '\0';
+
+ rc = sscanf(start, "%200s %200s%c", createOwner,
+ createGroup, &foo);
+ if (rc == 3) {
+ message(MESS_ERROR, "%s:%d extra arguments for "
+ "su\n", configFile, lineNum);
+ return 1;
+ }
+
+ if (rc > 0) {
+ pw = getpwnam(createOwner);
+ if (!pw) {
+ message(MESS_ERROR, "%s:%d unknown user '%s'\n",
+ configFile, lineNum, createOwner);
+ return 1;
+ }
+ newlog->suUid = pw->pw_uid;
+ endpwent();
+ }
+ if (rc > 1) {
+ group = getgrnam(createGroup);
+ if (!group) {
+ message(MESS_ERROR, "%s:%d unknown group '%s'\n",
+ configFile, lineNum, createGroup);
+ return 1;
+ }
+ newlog->suGid = group->gr_gid;
+ endgrent();
+ }
+
+ newlog->flags |= LOG_FLAG_SU;
+
+ *endtag = oldchar, start = endtag;
} else if (!strcmp(start, "create")) {
*endtag = oldchar, start = endtag;
diff --git a/logrotate.8 b/logrotate.8
index 93d5c6a..54a5460 100644
--- a/logrotate.8
+++ b/logrotate.8
@@ -460,6 +460,11 @@ be created with a .9, skipping 0-8. Files will still be rotated the
number of times specified with the \fBcount\fR directive.
.TP
+\fBsu \fIuser\fR \fIgroup\fR
+Rotate log files with the specified user and group instead of using
+the default (usually root).
+
+.TP
\fBtabooext\fR [+] \fIlist\fR
The current taboo extension list is changed (see the \fBinclude\fR directive
for information on the taboo extensions). If a + precedes the list of
diff --git a/logrotate.c b/logrotate.c
index aa405ee..5e4c27a 100644
--- a/logrotate.c
+++ b/logrotate.c
@@ -76,6 +76,8 @@ const char * compress_cmd_list[][2] = {
};
time_t nowSecs = 0;
+static uid_t save_euid;
+static gid_t save_egid;
static int shred_file(int fd, char *filename, struct logInfo *log);
@@ -88,6 +90,48 @@ static int globerr(const char *pathname, int theerr)
return 1;
}
+int switch_user(uid_t user, gid_t group) {
+ save_egid = getegid();
+ save_euid = geteuid();
+ if (save_euid == user && save_egid == group)
+ return 0;
+ message(MESS_DEBUG, "switching euid to %d and egid to %d\n",
+ user, group);
+ if (setegid(group) || seteuid(user)) {
+ message(MESS_ERROR, "error switching euid to %d and egid to %d: %s\n",
+ user, group, strerror(errno));
+ return 1;
+ }
+ return 0;
+}
+
+int switch_user_back() {
+ return switch_user(save_euid, save_egid);
+}
+
+int switch_user_permanently(const struct logInfo *log) {
+ gid_t group = getegid();
+ uid_t user = geteuid();
+ if (!(log->flags & LOG_FLAG_SU)) {
+ return 0;
+ }
+ if (getuid() == user && getgid() == group)
+ return 0;
+ // switch to full root first
+ if (setgid(getgid()) || setuid(getuid())) {
+ message(MESS_ERROR, "error getting rid of euid != uid\n");
+ return 1;
+ }
+ message(MESS_DEBUG, "switching uid to %d and gid to %d\n",
+ user, group);
+ if (setgid(group) || setuid(user)) {
+ message(MESS_ERROR, "error switching euid to %d and egid to %d: %s\n",
+ user, group, strerror(errno));
+ return 1;
+ }
+ return 0;
+}
+
static void unescape(char *arg)
{
char *p = arg;
@@ -215,7 +259,7 @@ static struct logState *findState(const char *fn)
return p;
}
-static int runScript(char *logfn, char *script)
+static int runScript(struct logInfo *log, char *logfn, char *script)
{
int rc;
@@ -226,6 +270,12 @@ static int runScript(char *logfn, char *script)
}
if (!fork()) {
+ if (log->flags & LOG_FLAG_SU) {
+ if (switch_user_back() != 0) {
+ exit(1);
+ }
+ }
+
execl("/bin/sh", "sh", "-c", script, "logrotate_script", logfn, NULL);
exit(1);
}
@@ -304,6 +354,10 @@ static int shred_file(int fd, char *filename, struct logInfo *log)
dup2(fd, 1);
close(fd);
+ if (switch_user_permanently(log) != 0) {
+ exit(1);
+ }
+
execvp(fullCommand[0], (void *) fullCommand);
exit(1);
}
@@ -325,7 +379,7 @@ static int removeLogFile(char *name, struct logInfo *log)
int fd;
message(MESS_DEBUG, "removing old log %s\n", name);
- if ((fd = open(name, O_RDWR)) < 0) {
+ if ((fd = open(name, O_RDWR | O_NOFOLLOW)) < 0) {
message(MESS_ERROR, "error opening %s: %s\n",
name, strerror(errno));
return 1;
@@ -366,7 +420,7 @@ static int compressLogFile(char *name, struct logInfo *log, struct stat *sb)
compressedName = alloca(strlen(name) + strlen(log->compress_ext) + 2);
sprintf(compressedName, "%s%s", name, log->compress_ext);
- if ((inFile = open(name, O_RDWR)) < 0) {
+ if ((inFile = open(name, O_RDWR | O_NOFOLLOW)) < 0) {
message(MESS_ERROR, "unable to open %s for compression\n", name);
return 1;
}
@@ -384,6 +438,10 @@ static int compressLogFile(char *name, struct logInfo *log, struct stat *sb)
dup2(outFile, 1);
close(outFile);
+ if (switch_user_permanently(log) != 0) {
+ exit(1);
+ }
+
execvp(fullCommand[0], (void *) fullCommand);
exit(1);
}
@@ -409,7 +467,7 @@ static int compressLogFile(char *name, struct logInfo *log, struct stat *sb)
return 0;
}
-static int mailLog(char *logFile, char *mailCommand,
+static int mailLog(const struct logInfo *log, char *logFile, char *mailCommand,
char *uncompressCommand, char *address, char *subject)
{
int mailInput;
@@ -419,7 +477,7 @@ static int mailLog(char *logFile, char *mailCommand,
char *mailArgv[] = { mailCommand, "-s", subject, address, NULL };
int rc = 0;
- if ((mailInput = open(logFile, O_RDONLY)) < 0) {
+ if ((mailInput = open(logFile, O_RDONLY | O_NOFOLLOW)) < 0) {
message(MESS_ERROR, "failed to open %s for mailing: %s\n", logFile,
strerror(errno));
return 1;
@@ -439,6 +497,9 @@ static int mailLog(char *logFile, char *mailCommand,
close(uncompressPipe[0]);
close(uncompressPipe[1]);
+ if (switch_user_permanently(log) != 0) {
+ exit(1);
+ }
execlp(uncompressCommand, uncompressCommand, NULL);
exit(1);
}
@@ -453,6 +514,13 @@ static int mailLog(char *logFile, char *mailCommand,
close(mailInput);
close(1);
+ // mail command runs as root
+ if (log->flags & LOG_FLAG_SU) {
+ if (switch_user_back() != 0) {
+ exit(1);
+ }
+ }
+
execvp(mailArgv[0], mailArgv);
exit(1);
}
@@ -488,12 +556,12 @@ static int mailLogWrapper(char *mailFilename, char *mailCommand,
if ((log->flags & LOG_FLAG_COMPRESS) &&
!((log->flags & LOG_FLAG_DELAYCOMPRESS) &&
(log->flags & LOG_FLAG_MAILFIRST))) {
- if (mailLog(mailFilename, mailCommand,
+ if (mailLog(log, mailFilename, mailCommand,
log->uncompress_prog, log->logAddress,
log->files[logNum]))
return 1;
} else {
- if (mailLog(mailFilename, mailCommand, NULL,
+ if (mailLog(log, mailFilename, mailCommand, NULL,
log->logAddress, mailFilename))
return 1;
}
@@ -510,7 +578,7 @@ static int copyTruncate(char *currLog, char *saveLog, struct stat *sb,
message(MESS_DEBUG, "copying %s to %s\n", currLog, saveLog);
if (!debug) {
- if ((fdcurr = open(currLog, (flags & LOG_FLAG_COPY) ? O_RDONLY : O_RDWR)) < 0) {
+ if ((fdcurr = open(currLog, ((flags & LOG_FLAG_COPY) ? O_RDONLY : O_RDWR) | O_NOFOLLOW)) < 0) {
message(MESS_ERROR, "error opening %s: %s\n", currLog,
strerror(errno));
return 1;
@@ -608,6 +676,36 @@ int findNeedRotating(struct logInfo *log, int logNum)
message(MESS_DEBUG, "considering log %s\n", log->files[logNum]);
+ /* Check if parent directory of this log has safe permissions */
+ if ((log->flags & LOG_FLAG_SU) == 0 && getuid() == 0) {
+ char *ld = ourDirName(log->files[logNum]);
+ if (stat(ld, &sb)) {
+ /* If parent directory doesn't exist, it's not real error
+ and rotation is not needed */
+ if (errno != ENOENT) {
+ message(MESS_ERROR, "stat of %s failed: %s\n", ld,
+ strerror(errno));
+ free(ld);
+ return 1;
+ }
+ free(ld);
+ return 0;
+ }
+ /* Don't rotate in directories writable by others or group which is not "root" */
+ if (sb.st_uid != 0 || (sb.st_gid != 0 && sb.st_mode & S_IWGRP) || sb.st_mode & S_IWOTH) {
+ message(MESS_ERROR, "\"%s\" has insecure permissions."
+ " It must be owned and be writable by root only to avoid security problems."
+ " Set the \"su\" directive in the config file to tell logrotate which user/group"
+ " should be used for rotation.\n"
+ , ld);
+#if 0 // accept nevertheless for now
+ free(ld);
+ return 0;
+#endif
+ }
+ free(ld);
+ }
+
if (stat(log->files[logNum], &sb)) {
if ((log->flags & LOG_FLAG_MISSINGOK) && (errno == ENOENT)) {
message(MESS_DEBUG, " log %s does not exist -- skipping\n",
@@ -1114,7 +1212,7 @@ int rotateSingleLog(struct logInfo *log, int logNum, struct logState *state,
security_context_t oldContext = NULL;
int fdcurr = -1;
- if ((fdcurr = open(log->files[logNum], O_RDWR)) < 0) {
+ if ((fdcurr = open(log->files[logNum], O_RDWR | O_NOFOLLOW)) < 0) {
message(MESS_ERROR, "error opening %s: %s\n",
log->files[logNum],
strerror(errno));
@@ -1333,6 +1431,12 @@ int rotateLogSet(struct logInfo *log, int force)
message(MESS_DEBUG, "old logs are removed\n");
}
+ if (log->flags & LOG_FLAG_SU) {
+ if (switch_user(log->suUid, log->suGid) != 0) {
+ return 1;
+ }
+ }
+
for (i = 0; i < log->numFiles; i++) {
logHasErrors[i] = findNeedRotating(log, i);
hasErrors |= logHasErrors[i];
@@ -1348,10 +1452,17 @@ int rotateLogSet(struct logInfo *log, int force)
"since no logs will be rotated\n");
} else {
message(MESS_DEBUG, "running first action script\n");
- if (runScript(log->pattern, log->first)) {
+ if (runScript(log, log->pattern, log->first)) {
message(MESS_ERROR, "error running first action script "
"for %s\n", log->pattern);
hasErrors = 1;
+
+ if (log->flags & LOG_FLAG_SU) {
+ if (switch_user_back() != 0) {
+ return 1;
+ }
+ }
+
/* finish early, firstaction failed, affects all logs in set */
return hasErrors;
}
@@ -1386,7 +1497,7 @@ int rotateLogSet(struct logInfo *log, int force)
"since no logs will be rotated\n");
} else {
message(MESS_DEBUG, "running prerotate script\n");
- if (runScript(log->flags & LOG_FLAG_SHAREDSCRIPTS ? log->pattern : log->files[j], log->pre)) {
+ if (runScript(log, log->flags & LOG_FLAG_SHAREDSCRIPTS ? log->pattern : log->files[j], log->pre)) {
if (log->flags & LOG_FLAG_SHAREDSCRIPTS)
message(MESS_ERROR,
"error running shared prerotate script "
@@ -1421,7 +1532,7 @@ int rotateLogSet(struct logInfo *log, int force)
"since no logs were rotated\n");
} else {
message(MESS_DEBUG, "running postrotate script\n");
- if (runScript(log->flags & LOG_FLAG_SHAREDSCRIPTS ? log->pattern : log->files[j], log->post)) {
+ if (runScript(log, log->flags & LOG_FLAG_SHAREDSCRIPTS ? log->pattern : log->files[j], log->post)) {
if (log->flags & LOG_FLAG_SHAREDSCRIPTS)
message(MESS_ERROR,
"error running shared postrotate script "
@@ -1467,7 +1578,7 @@ int rotateLogSet(struct logInfo *log, int force)
"since no logs will be rotated\n");
} else {
message(MESS_DEBUG, "running last action script\n");
- if (runScript(log->pattern, log->last)) {
+ if (runScript(log, log->pattern, log->last)) {
message(MESS_ERROR, "error running last action script "
"for %s\n", log->pattern);
hasErrors = 1;
@@ -1475,6 +1586,12 @@ int rotateLogSet(struct logInfo *log, int force)
}
}
+ if (log->flags & LOG_FLAG_SU) {
+ if (switch_user_back() != 0) {
+ return 1;
+ }
+ }
+
return hasErrors;
}
diff --git a/logrotate.h b/logrotate.h
index 24e3ec0..1ac94b3 100644
--- a/logrotate.h
+++ b/logrotate.h
@@ -18,6 +18,7 @@
#define LOG_FLAG_COPY (1 << 8)
#define LOG_FLAG_DATEEXT (1 << 9)
#define LOG_FLAG_SHRED (1 << 10)
+#define LOG_FLAG_SU (1 << 11)
#define NO_MODE ((mode_t) -1)
#define NO_UID ((uid_t) -1)
@@ -51,6 +52,8 @@ struct logInfo {
mode_t createMode; /* if any/all of these are -1, we use the */
uid_t createUid; /* attributes from the log file just rotated */
gid_t createGid;
+ uid_t suUid; /* switch user to this uid and group to this gid */
+ gid_t suGid;
/* these are at the end so they end up nil */
const char **compress_options_list;
int compress_options_count;
--
1.7.7