From 35d2e11a0458a79c89816a2f0be6fb957f91873e Mon Sep 17 00:00:00 2001 From: Balbir Singh Date: Fri, 26 Sep 2008 11:56:34 +0000 Subject: Merge the cgruleseng daemon from Steve Olivieri Signed-off-by: Steve Olivieri Signed-off-by: Balbir Singh git-svn-id: https://libcg.svn.sourceforge.net/svnroot/libcg/trunk@190 4f4bb910-9a46-0410-90c8-c897d4f1cd53 --- Makefile | 26 +- Makefile.in | 14 +- README_daemon | 178 ++++++++++++ api.c | 808 +++++++++++++++++++++++++++++++++++++++------------ cgrulesengd.c | 625 +++++++++++++++++++++++++++++++++++++++ cgrulesengd.h | 124 ++++++++ libcgroup-internal.h | 18 ++ libcgroup.h | 79 ++++- samples/cgred.conf | 19 ++ scripts/init.d/cgred | 129 ++++++++ tests/Makefile | 7 +- tests/setuid.c | 75 +++++ 12 files changed, 1906 insertions(+), 196 deletions(-) create mode 100644 README_daemon create mode 100644 cgrulesengd.c create mode 100644 cgrulesengd.h create mode 100644 samples/cgred.conf create mode 100644 scripts/init.d/cgred create mode 100644 tests/setuid.c diff --git a/Makefile b/Makefile index 2819e8b..380dde7 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ bindir=${exec_prefix}/bin sbindir=${exec_prefix}/sbin libdir=${exec_prefix}/lib includedir=${prefix}/include -prefix=/usr/local +prefix=/usr exec_prefix=${prefix} INSTALL=install INSTALL_DATA=install -m 644 @@ -29,18 +29,22 @@ PACKAGE_VERSION=0.31 CFLAGS=-g -O2 $(INC) -DPACKAGE_VERSION=$(PACKAGE_VERSION) VERSION=1 -all: libcgroup.so cgconfigparser cgexec cgclassify +all: libcgroup.so cgconfigparser cgexec cgclassify cgrulesengd cgconfigparser: libcgroup.so config.c y.tab.c lex.yy.c libcgroup.h file-ops.c $(CC) $(CFLAGS) -o $@ y.tab.c lex.yy.c config.c file-ops.c \ - $(LDFLAGS) $(LIBS) + $(LDFLAGS) $(LIBS) cgexec: libcgroup.so cgexec.c libcgroup.h $(CC) $(CFLAGS) -Wall -o $@ cgexec.c $(LDFLAGS) $(LIBS) -cgclassify: cgclassify.c +cgclassify: libcgroup.so cgclassify.c $(CC) $(CFLAGS) -Wall -o $@ cgclassify.c $(LDFLAGS) $(LIBS) +cgrulesengd: libcgroup.so libcgroup.h cgrulesengd.c cgrulesengd.h + $(CC) -std=gnu99 $(DEBUG) $(CFLAGS) -Wall -o $@ cgrulesengd.c \ + $(LDFLAGS) $(LIBS) + y.tab.c: parse.y lex.yy.c $(YACC) -v -d parse.y @@ -49,7 +53,7 @@ lex.yy.c: lex.l libcgroup.so: api.c libcgroup.h wrapper.c $(CC) $(CFLAGS) -shared -fPIC -Wl,--soname,$@.$(VERSION) -o $@ api.c \ - wrapper.c + wrapper.c ln -sf $@ $@.$(VERSION) test: @@ -59,14 +63,15 @@ pam_cgroup.so: pam_cgroup.c $(CC) $(CFLAGS) -shared -fPIC -Wall -o $@ pam_cgroup.c $(LDFLAGS) \ $(LIBS) -lpam -install: libcgroup.so cgexec cgclassify +install: libcgroup.so cgexec cgclassify cgconfigparser $(INSTALL_DATA) -D libcgroup.h $(DESTDIR)$(includedir)/libcgroup.h $(INSTALL) -D libcgroup.so $(DESTDIR)$(libdir)/libcgroup-$(PACKAGE_VERSION).so ln -sf libcgroup-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libcgroup.so.$(VERSION) ln -sf libcgroup.so.$(VERSION) $(DESTDIR)$(libdir)/libcgroup.so $(INSTALL) -D cgconfigparser $(DESTDIR)$(sbindir) - $(INSTALL) cgexec $(DESTDIR)$(bindir)/cgexec - $(INSTALL) cgclassify $(DESTDIR)$(bindir)/cgclassify + $(INSTALL) -D cgexec $(DESTDIR)$(bindir)/cgexec + $(INSTALL) -D cgclassify $(DESTDIR)$(bindir)/cgclassify + $(INSTALL) -D cgrulesengd $(DESTDIR)$(bindir)/cgrulesengd uninstall: libcgroup.so rm -f $(DESTDIR)$(includedir)/libcgroup.h @@ -76,8 +81,9 @@ uninstall: libcgroup.so rm -f $(DESTDIR)$(sbindir)/cgconfigparser rm -f $(DESTDIR)$(bindir)/cgexec rm -f $(DESTDIR)$(bindir)/cgclassify + rm -f $(DESTDIR)$(bindir)/cgrulesengd clean: - \rm -f y.tab.c y.tab.h lex.yy.c y.output libcgroup.so cgclassify\ + \rm -f y.tab.c y.tab.h lex.yy.c y.output libcgroup.so cgclassify \ libcgroup.so.$(VERSION) cgconfigparser config.log config.status cgexec \ - pam_cgroup.so + pam_cgroup.so cgrulesengd diff --git a/Makefile.in b/Makefile.in index f875578..aa68f4e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -29,11 +29,11 @@ PACKAGE_VERSION=@PACKAGE_VERSION@ CFLAGS=@CFLAGS@ $(INC) -DPACKAGE_VERSION=$(PACKAGE_VERSION) VERSION=1 -all: libcgroup.so cgconfigparser cgexec cgclassify +all: libcgroup.so cgconfigparser cgexec cgclassify cgrulesengd cgconfigparser: libcgroup.so config.c y.tab.c lex.yy.c libcgroup.h file-ops.c $(CC) $(CFLAGS) -o $@ y.tab.c lex.yy.c config.c file-ops.c \ - $(LDFLAGS) $(LIBS) + $(LDFLAGS) $(LIBS) cgexec: libcgroup.so cgexec.c libcgroup.h $(CC) $(CFLAGS) -Wall -o $@ cgexec.c $(LDFLAGS) $(LIBS) @@ -41,6 +41,10 @@ cgexec: libcgroup.so cgexec.c libcgroup.h cgclassify: libcgroup.so cgclassify.c $(CC) $(CFLAGS) -Wall -o $@ cgclassify.c $(LDFLAGS) $(LIBS) +cgrulesengd: libcgroup.so libcgroup.h cgrulesengd.c cgrulesengd.h + $(CC) -std=gnu99 $(DEBUG) $(CFLAGS) -Wall -o $@ cgrulesengd.c \ + $(LDFLAGS) $(LIBS) + y.tab.c: parse.y lex.yy.c $(YACC) -v -d parse.y @@ -49,7 +53,7 @@ lex.yy.c: lex.l libcgroup.so: api.c libcgroup.h wrapper.c $(CC) $(CFLAGS) -shared -fPIC -Wl,--soname,$@.$(VERSION) -o $@ api.c \ - wrapper.c + wrapper.c ln -sf $@ $@.$(VERSION) test: @@ -67,6 +71,7 @@ install: libcgroup.so cgexec cgclassify cgconfigparser $(INSTALL) -D cgconfigparser $(DESTDIR)$(sbindir) $(INSTALL) -D cgexec $(DESTDIR)$(bindir)/cgexec $(INSTALL) -D cgclassify $(DESTDIR)$(bindir)/cgclassify + $(INSTALL) -D cgrulesengd $(DESTDIR)$(bindir)/cgrulesengd uninstall: libcgroup.so rm -f $(DESTDIR)$(includedir)/libcgroup.h @@ -76,8 +81,9 @@ uninstall: libcgroup.so rm -f $(DESTDIR)$(sbindir)/cgconfigparser rm -f $(DESTDIR)$(bindir)/cgexec rm -f $(DESTDIR)$(bindir)/cgclassify + rm -f $(DESTDIR)$(bindir)/cgrulesengd clean: \rm -f y.tab.c y.tab.h lex.yy.c y.output libcgroup.so cgclassify \ libcgroup.so.$(VERSION) cgconfigparser config.log config.status cgexec \ - pam_cgroup.so + pam_cgroup.so cgrulesengd diff --git a/README_daemon b/README_daemon new file mode 100644 index 0000000..1ff7a33 --- /dev/null +++ b/README_daemon @@ -0,0 +1,178 @@ +DESCRIPTION +=========== +The CGroup Rules Engine Daemon is a tool that will automatically place tasks +into the correct cgroup based on UID/GID events from the kernel. It will not +automatically classify tasks that are already running, but it will classify +any new tasks, and any tasks which change their UID/GID. Note that we use +the euid and egid, not the ruid and rgid. + +Unlike other tools, cgrulesengd caches the rules configuration in a data +structure (it's actually just a FIFO linked list) so that it doesn't need to +parse the configuration file more than once. This should be much faster than +parsing the rules for each UID/GID event. Eventually, this caching logic +should be part of libcgroup, so that any other program can take advantage of it +(and so that all programs are using the same table). The configuration can +be reloaded without stopping the daemon (more information below). + +WHY A DAEMON? +============= +A daemon is easy to use, and allows an administrator to ensure that all tasks +are classified into the correct cgroups without constantly monitoring the +system. The daemon is transparent to the users, and does not require any +modifications to existing userspace programs. Finally, the daemon can be +started and stopped at any time, including at boot time with other services. +Thus, sytem administrators can decide not to use the daemon if they choose. + +Most importantly, some programs create new users and/or run scripts, +threads, etc. as those users using suexec(). This call does not go through +PAM, so these scripts would continue running in the same cgroup as the parent +program. This behavior is likely not ideal, and the daemon would solve this +problem. + +Apache does this. Apache creates a user called 'apache' and uses setuid() to +launch tasks as that user. This does not go through PAM, so without a daemon, +these tasks would continue to run in the 'root' cgroup rather than in the +'apache' or 'webserver' cgroup. The daemon fixes this problem by catching the +setuid() call and moving the tasks into the correct cgroup. + +We would ask Apache to modify their software to interface with libcgroup, but +this solution is less than optimal because a lot of userspace software would +have to be changed, and some authors might intentionally not interact with +libcgroup, which could create an exploit. The daemon is a simple, transparent +solution. + +USING THE DAEMON +================ +The daemon can be used as a service with the cgred script, which is shipped +as scripts/init.d/cgred. This script should be installed as /etc/init.d/cgred +and used like any other service. To start the daemon, + /etc/init.d/cgred start +To stop it, + /etc/init.d/cgred stop +The restart (stop,start), condrestart (same as restart, but only if the daemon +was already started), and status (print whether the daemon is started or +stopped) commands are also supported. An additional command, "flash", allows +you to reload the configuration file without stopping the daemon. + /etc/init.d/cgred flash +The cgred script automatically loads configuration from /etc/cgred.d/cgred.conf, +which is shipped as samples/cgred.conf. See that file for more information. + +If you choose not to run the daemon as a service, the following options are +currently supported: + --nodaemon Do not run as a daemon + --nolog Write log output to stdout instead of a log file + --config [FILE] Read rules configuration from FILE instead of + /etc/cgrules.conf + +You can ask the daemon to reload the configuration by sending it SIGUSR2. The +easiest way to do this is with the 'kill' command: + kill -s SIGUSR2 [PID] + +TESTING +======= +The program setuid (found in tests/setuid.c) can help you test the daemon. By +default, this program attempts to change its UID to root and then idles until +you kill it. You can change the default behavior to use a different UID, or +you can uncomment the second block of code to instead attempt to change the +GID. + +In order to make sure that everything works, I used the following rules: + sjo cpu default + cgtest cpu cgtest + % memory default + @cgroup cpu,memory cgtest + peter cpu test1 + % memory test2 + @root * default + * * default + +The users 'sjo' and 'cgtest' were normal users. 'peter' is not a user on the +system. The group 'cgroup' is a group containing sjo,root,cgtest as members, +and the group 'root' contains only root. The cgroups 'default' and 'cgtest' +exist, while 'test1' and 'test2' do not. Currently, the daemon does not check +for the existance of 'test1', though this would be easier to do once the +parsing and caching logic is moved into libcgroup. + +I ran the following tests, all of which were successful: + - set UID to sjo (should move cpu controller into default) + - set UID to root (should move cpu,memory controllers into cgtest) + - set UID to cgtest (should move cpu controller into cgtest, memory + controller into default) + - set GID to root (should move all controllers into default) + - set GID to cgroup (should move cpu, memory into cgtest) + - set GID to users (should move all controllers into default) + +The parsing logic will skip the 'peter' rule as well as its multi-line +components (in this case "% memory test2"), because the user does not exist. +This should work for group rules, too. Attempting to setuid() or setgid() to a +user/group that doesn't exist will just return an error and not generate a +kernel event of the PROC_EVENT_UID or PROC_EVENT_GID type, so the daemon won't +do anything for it. + +CONCERNS/ISSUES +=============== + - Netlink can be unreliable, and the daemon might miss an event if the buffer + is full. One possible solution is to have one or two files that the kernel + can queue UID/GID changes in, and have the daemon read those files whenever + they are updated. From testing, this does not actually appear to be a real + problem, but it could become one with faster machines. + - The daemon does not care for namespaces at all, which can cause conflicts + with containers. If a user places his tasks into exec-based cgroups (such + as 'network' and 'development'), the daemon will not realize this and will + simply place them into the user's cgroup (so, sjo/ instead of sjo/network/). + +CHANGELOG +========= +V9: + - Updated documentation, because it was very old and incorrect. + - Reverted the changes to cgexec and cgclassify. + - New API function: cgroup_change_cgroup_uid_gid_flags(). + - Deprecated cgroup_change_cgroup_uid_gid(). + - Rewrote some of the rule matching and execution logic in api.c to be + faster, and easier to read. + - Changes all negative return values to positive values. As a side effect, + cgroup_parse_rules() now returns -1 when we get a match and we are using + non-cached rules. + - Changes CGROUP_FUSECACHE to CGFLAG_USECACHE. + - Flags are now enumerated (cgflags), instead of #defines. + +V8: + - Moved the event-handling logic back into the daemon, where it should be. + - Changed cgroup_parse_rules() to work with cached rules or non-cached rules. + The other parsing function is no longer needed, and should be deprecated. + - Non-cached rules now work with the same structs as cached rules. + - Modified cgroup_change_cgroup_uid_gid() with a new 'flags' parameter. + Currently, the only flag is "CGROUP_FUSECACHE" to use the cached rules logic + (or not). + - Added cgroup_rules_loaded() boolean, to check whether the cached rules have + been loaded yet, and cgroup_init_rules_cache() to load them. + - Modified cgexec and cgclassify to work with the new + cgroup_change_cgroup_uid_gid(). + +V7: + - Moved parsing and caching logic into libcgroup. + - Added locking mechanisms around the list of rules. + - Cleaned up #includes in cgrulesegnd.[h,c]. + - Added notification if netlink receive queue overflows. + - Added logic to catch SIGINT in addition to SIGTERM. + - New API functions: + - cgroup_free_rule(struct cgroup_rule*) + - cgroup_free_rule_list(struct cgroup_rule_list*) + - cgroup_parse_rules(void) + - cgroup_print_rules_config(FILE*) + - cgroup_reload_cached_rules(void) + - cgroup_change_cgroup_event(struct proc_event*, int, FILE*) + +V6: + - Wrote new parsing logic, which is cleaner and simpler. + - Added cgred script to enable using the daemon as a service. + - Wrote caching logic to cache rules table. + - Added the ability to force a reload of the rules table with SIGUSR2 signal. + - Added two structures to libcgroup: cgre_rule and cgre_rules_list + - New API function: cgroup_reload_cached_rules, which reloads the rules table. + - Added logging capabilities (default log is /root/cgrulesengd.conf) + +TODO +==== + - Find a way to replace Netlink, or at least clean up that code. + - Find a solution to the namespace problem. diff --git a/api.c b/api.c index fc7e18a..60a458c 100644 --- a/api.c +++ b/api.c @@ -58,6 +58,18 @@ static pthread_rwlock_t cg_mount_table_lock = PTHREAD_RWLOCK_INITIALIZER; /* Check if cgroup_init has been called or not. */ static int cgroup_initialized; +/* Check if the rules cache has been loaded or not. */ +static bool cgroup_rules_loaded; + +/* List of configuration rules */ +static struct cgroup_rule_list rl; + +/* Temporary list of configuration rules (for non-cache apps) */ +static struct cgroup_rule_list trl; + +/* Lock for the list of rules (rl) */ +static pthread_rwlock_t rl_lock = PTHREAD_RWLOCK_INITIALIZER; + static int cg_chown_file(FTS *fts, FTSENT *ent, uid_t owner, gid_t group) { int ret = 0; @@ -120,6 +132,359 @@ static int cgroup_test_subsys_mounted(const char *name) return 0; } +/** + * Free a single cgroup_rule struct. + * @param r The rule to free from memory + */ +static void cgroup_free_rule(struct cgroup_rule *r) +{ + /* Loop variable */ + int i = 0; + + /* Make sure our rule is not NULL, first. */ + if (!r) { + dbg("Warning: Attempted to free NULL rule.\n"); + return; + } + + /* We must free any used controller strings, too. */ + for(i = 0; i < MAX_MNT_ELEMENTS; i++) { + if (r->controllers[i]) + free(r->controllers[i]); + } + + free(r); +} + +/** + * Free a list of cgroup_rule structs. If rl is the main list of rules, + * the lock must be taken for writing before calling this function! + * @param rl Pointer to the list of rules to free from memory + */ +static void cgroup_free_rule_list(struct cgroup_rule_list *rl) +{ + /* Temporary pointer */ + struct cgroup_rule *tmp = NULL; + + /* Make sure we're not freeing NULL memory! */ + if (!(rl->head)) { + dbg("Warning: Attempted to free NULL list.\n"); + return; + } + + while (rl->head) { + tmp = rl->head; + rl->head = tmp->next; + cgroup_free_rule(tmp); + } + + /* Don't leave wild pointers around! */ + rl->head = NULL; + rl->tail = NULL; +} + +/** + * Parse the configuration file that maps UID/GIDs to cgroups. If ever the + * configuration file is modified, applications should call this function to + * load the new configuration rules. The function caller is responsible for + * calling free() on each rule in the list. + * + * The cache parameter alters the behavior of this function. If true, this + * function will read the entire configuration file and store the results in + * rl (global rules list). If false, this function will only parse until it + * finds a rule matching the given UID or GID. It will store this rule in rl, + * as well as any children rules (rules that begin with a %) that it has. + * + * This function is NOT thread safe! + * @param cache True to cache rules, else false + * @param muid If cache is false, the UID to match against + * @param mgid If cache is false, the GID to match against + * @return 0 on success, -1 if no cache and match found, > 0 on error. + * TODO: Make this function thread safe! + */ +static int cgroup_parse_rules(bool cache, uid_t muid, gid_t mgid) +{ + /* File descriptor for the configuration file */ + FILE *fp = NULL; + + /* Buffer to store the line we're working on */ + char *buff = NULL; + + /* Iterator for the line we're working on */ + char *itr = NULL; + + /* Pointer to the list that we're using */ + struct cgroup_rule_list *lst = NULL; + + /* Rule to add to the list */ + struct cgroup_rule *newrule = NULL; + + /* Structure to get GID from group name */ + struct group *grp = NULL; + + /* Structure to get UID from user name */ + struct passwd *pwd = NULL; + + /* Temporary storage for a configuration rule */ + char user[LOGIN_NAME_MAX] = { '\0' }; + char controllers[CG_CONTROLLER_MAX] = { '\0' }; + char destination[FILENAME_MAX] = { '\0' }; + uid_t uid = CGRULE_INVALID; + gid_t gid = CGRULE_INVALID; + + /* The current line number */ + unsigned int linenum = 0; + + /* Did we skip the previous line? */ + bool skipped = false; + + /* Have we found a matching rule (non-cache mode)? */ + bool matched = false; + + /* Return codes */ + int ret = 0; + + /* Temporary buffer for strtok() */ + char *stok_buff = NULL; + + /* Loop variable. */ + int i = 0; + + /* Open the configuration file. */ + pthread_rwlock_wrlock(&rl_lock); + fp = fopen(CGRULES_CONF_FILE, "r"); + if (!fp) { + dbg("Failed to open configuration file %s with" + " error: %s\n", CGRULES_CONF_FILE, + strerror(errno)); + ret = errno; + goto finish; + } + + buff = calloc(CGROUP_RULE_MAXLINE, sizeof(char)); + if (!buff) { + dbg("Out of memory? Error: %s\n", strerror(errno)); + ret = errno; + goto close_unlock; + } + + /* Determine which list we're using. */ + if (cache) + lst = &rl; + else + lst = &trl; + + /* If our list already exists, clean it. */ + if (lst->head) + cgroup_free_rule_list(lst); + + /* Now, parse the configuration file one line at a time. */ + dbg("Parsing configuration file.\n"); + while ((itr = fgets(buff, CGROUP_RULE_MAXLINE, fp)) != NULL) { + linenum++; + + /* We ignore anything after a # sign as comments. */ + if ((itr = strchr(buff, '#'))) + *itr = '\0'; + + /* We also need to remove the newline character. */ + if ((itr = strchr(buff, '\n'))) + *itr = '\0'; + + /* Now, skip any leading tabs and spaces. */ + itr = buff; + while (itr && isblank(*itr)) + itr++; + + /* If there's nothing left, we can ignore this line. */ + if (!strlen(itr)) + continue; + + /* + * If we skipped the last rule and this rule is a continuation + * of it (begins with %), then we should skip this rule too. + */ + if (skipped && *itr == '%') { + dbg("Warning: Skipped child of invalid rule," + " line %d.\n", linenum); + memset(buff, '\0', CGROUP_RULE_MAXLINE); + continue; + } + + /* + * If there is something left, it should be a rule. Otherwise, + * there's an error in the configuration file. + */ + skipped = false; + memset(user, '\0', LOGIN_NAME_MAX); + memset(controllers, '\0', CG_CONTROLLER_MAX); + memset(destination, '\0', FILENAME_MAX); + i = sscanf(itr, "%s%s%s", user, controllers, destination); + if (i != 3) { + dbg("Failed to parse configuration file on" + " line %d.\n", linenum); + goto parsefail; + } + + /* + * Next, check the user/group. If it's a % sign, then we + * are continuing another rule and UID/GID should not be + * reset. If it's a @, we're dealing with a GID rule. If + * it's a *, then we do not need to do a lookup because the + * rule always applies (it's a wildcard). If we're using + * non-cache mode and we've found a matching rule, we only + * continue to parse if we're looking at a child rule. + */ + if ((!cache) && matched && (strncmp(user, "%", 1) != 0)) { + /* If we make it here, we finished (non-cache). */ + dbg("Parsing of configuration file complete.\n\n"); + ret = -1; + goto cleanup; + } + if (strncmp(user, "@", 1) == 0) { + /* New GID rule. */ + itr = &(user[1]); + if ((grp = getgrnam(itr))) { + uid = CGRULE_INVALID; + gid = grp->gr_gid; + } else { + dbg("Warning: Entry for %s not" + "found. Skipping rule on line" + " %d.\n", itr, linenum); + memset(buff, '\0', CGROUP_RULE_MAXLINE); + skipped = true; + continue; + } + } else if (strncmp(user, "*", 1) == 0) { + /* Special wildcard rule. */ + uid = CGRULE_WILD; + gid = CGRULE_WILD; + } else if (*itr != '%') { + /* New UID rule. */ + if ((pwd = getpwnam(user))) { + uid = pwd->pw_uid; + gid = CGRULE_INVALID; + } else { + dbg("Warning: Entry for %s not" + "found. Skipping rule on line" + " %d.\n", user, linenum); + memset(buff, '\0', CGROUP_RULE_MAXLINE); + skipped = true; + continue; + } + } /* Else, we're continuing another rule (UID/GID are okay). */ + + /* + * If we are not caching rules, then we need to check for a + * match before doing anything else. We consider four cases: + * The UID matches, the GID matches, the UID is a member of the + * GID, or we're looking at the wildcard rule, which always + * matches. If none of these are true, we simply continue to + * the next line in the file. + */ + if (grp && muid != CGRULE_INVALID) { + pwd = getpwuid(muid); + for (i = 0; grp->gr_mem[i]; i++) { + if (!(strcmp(pwd->pw_name, grp->gr_mem[i]))) + matched = true; + } + } + + if (uid == muid || gid == mgid || uid == CGRULE_WILD) { + matched = true; + } + + if (!cache && !matched) + continue; + + /* + * Now, we're either caching rules or we found a match. Either + * way, copy everything into a new rule and push it into the + * list. + */ + newrule = calloc(1, sizeof(struct cgroup_rule)); + if (!newrule) { + dbg("Out of memory? Error: %s\n", strerror(errno)); + ret = errno; + goto cleanup; + } + + newrule->uid = uid; + newrule->gid = gid; + strncpy(newrule->name, user, strlen(user)); + strncpy(newrule->destination, destination, strlen(destination)); + newrule->next = NULL; + + /* Parse the controller list, and add that to newrule too. */ + stok_buff = strtok(controllers, ","); + if (!stok_buff) { + dbg("Failed to parse controllers on line" + " %d\n", linenum); + goto destroyrule; + } + + i = 0; + do { + if (i >= MAX_MNT_ELEMENTS) { + dbg("Too many controllers listed" + " on line %d\n", linenum); + goto destroyrule; + } + + newrule->controllers[i] = strndup(stok_buff, + strlen(stok_buff) + 1); + if (!(newrule->controllers[i])) { + dbg("Out of memory? Error was: %s\n", + strerror(errno)); + goto destroyrule; + } + i++; + } while ((stok_buff = strtok(NULL, ","))); + + /* Now, push the rule. */ + if (lst->head == NULL) { + lst->head = newrule; + lst->tail = newrule; + } else { + lst->tail->next = newrule; + lst->tail = newrule; + } + + dbg("Added rule %s (UID: %d, GID: %d) -> %s for controllers:", + lst->tail->name, lst->tail->uid, lst->tail->gid, + lst->tail->destination); + for (i = 0; lst->tail->controllers[i]; i++) { + dbg(" %s", lst->tail->controllers[i]); + } + dbg("\n"); + + /* Finally, clear the buffer. */ + memset(buff, '\0', CGROUP_RULE_MAXLINE); + grp = NULL; + pwd = NULL; + } + + /* If we make it here, there were no errors. */ + dbg("Parsing of configuration file complete.\n\n"); + ret = (matched && !cache) ? -1 : 0; + goto cleanup; + +destroyrule: + cgroup_free_rule(newrule); + +parsefail: + ret = ECGROUPPARSEFAIL; + +cleanup: + free(buff); + +close_unlock: + fclose(fp); + pthread_rwlock_unlock(&rl_lock); +finish: + return ret; +} + /** * cgroup_init(), initializes the MOUNT_POINT. * @@ -293,7 +658,7 @@ static char *cg_build_path_locked(char *name, char *path, char *type) return NULL; } -char *cg_build_path(char *name, char *path, char *type) +static char *cg_build_path(char *name, char *path, char *type) { pthread_rwlock_rdlock(&cg_mount_table_lock); path = cg_build_path_locked(name, path, type); @@ -301,6 +666,7 @@ char *cg_build_path(char *name, char *path, char *type) return path; } + /** cgroup_attach_task_pid is used to assign tasks to a cgroup. * struct cgroup *cgroup: The cgroup to assign the thread to. * pid_t tid: The thread to be assigned to the cgroup. @@ -771,7 +1137,7 @@ free_parent: /** * @cgroup: cgroup data structure to be filled with parent values and then - * passed down for creation + * passed down for creation * @ignore_ownership: Ignore doing a chown on the newly created cgroup */ int cgroup_create_cgroup_from_parent(struct cgroup *cgroup, @@ -1225,216 +1591,197 @@ static void cg_free_controller_array(char *controllers[]) } } -/** cg_parse_rules_config_file - * parses the config file and determines the rule application based on - * uid and gid. +/** + * Finds the first rule in the cached list that matches the given UID or GID, + * and returns a pointer to that rule. This function uses rl_lock. * - * returns 0 on success. + * This function may NOT be thread safe. + * @param uid The UID to match + * @param gid The GID to match + * @return Pointer to the first matching rule, or NULL if no match + * TODO: Determine thread-safeness and fix if not safe. */ -static int cg_parse_rules_config_file(struct cgroup_rules_data *cgrldp, - struct cgroup *cgroups[]) +static struct cgroup_rule *cgroup_find_matching_rule_uid_gid(const uid_t uid, + const gid_t gid) { - FILE *fp; - char buf[FILENAME_MAX]; - char user[FILENAME_MAX]; - char dest[PATH_MAX]; - char buf_ctrl[FILENAME_MAX]; - char *controllers[CG_CONTROLLER_MAX]; - struct cgroup *cgroup; - int cgindex = 0, match_uid = 0, match_gid = 0, i, ret = 0; - - memset(controllers, 0, CG_CONTROLLER_MAX); - memset(buf_ctrl, 0, FILENAME_MAX); + /* Return value */ + struct cgroup_rule *ret = rl.head; - fp = fopen(CGRULES_CONF_FILE, "r"); - if (fp == NULL) { - dbg("Open of file %s failed: %s", CGRULES_CONF_FILE, - strerror(errno)); - return ECGOTHER; - } + /* Temporary user data */ + struct passwd *usr = NULL; - /* In case of multi line rule, we need to prepare multiple - * cgroups structure. That's why caller has passed an array - * of cgroup pointers. Keep a index of current empty cgroup - * structure which can be passed to cg_prepare_cgroup. - */ - cgindex = 0; - - /* Parse file */ - while (fgets(buf, FILENAME_MAX, fp) != NULL) { - char *tptr, *line; - struct group *group; - - line = buf; - /* skip the leading white space */ - while (*line && isspace(*line)) - line++; - - /* Rip off the comments */ - tptr = strchr(line, '#'); - if (tptr) - *tptr = '\0'; - - /* Rip off the newline char */ - tptr = strchr(line, '\n'); - if (tptr) - *tptr = '\0'; - - /* Anything left ? */ - if (!strlen(line)) - continue; + /* Temporary group data */ + struct group *grp = NULL; - user[0] = dest[0] = buf_ctrl[0] = '\0'; + /* Temporary string pointer */ + char *sp = NULL; - i = sscanf(line, "%s%s%s", user, buf_ctrl, dest); - dbg("scanned line[%d]: user[%s], controllers[%s]," - " dest[%s]\n", i, user, buf_ctrl, dest); + /* Loop variable */ + int i = 0; - /* If we encounter a rule which does not begin with %, - * and either match_uid or match_gid is set, that means - * we have processed one rule and if that rule was muti - * line then it has ended. Return back. Remember, we execute - * only first matching rule (either single line or multiline) - */ - if ((match_uid || match_gid) && strcmp(user, "%")) { - match_uid = 0; - match_gid = 0; - fclose(fp); - return 0; + pthread_rwlock_wrlock(&rl_lock); + while (ret) { + /* The wildcard rule always matches. */ + if ((ret->uid == CGRULE_WILD) && (ret->gid == CGRULE_WILD)) { + goto finished; } - if (i == CGRULES_MAX_FIELDS_PER_LINE) { - /* a complete line */ - if (((strcmp(cgrldp->pw->pw_name, user) == 0) || - (strcmp(user, "*") == 0)) || - (match_uid && !strcmp(user, "%"))) { - match_uid = 1; - - cgroup = calloc(1, sizeof(struct cgroup)); - if (!cgroup) { - ret = ECGOTHER; - goto out; - } - cgroups[cgindex] = cgroup; - cgindex++; - - ret = cg_prepare_controller_array(buf_ctrl, - controllers); - if (ret) - goto out; - ret = cg_prepare_cgroup(cgroup, cgrldp->pid, - dest, controllers); - if (ret) - goto out; - - cg_free_controller_array(controllers); - } else if (user[0] == '@' || - (match_gid && !strcmp(user, "%"))) { - errno = 0; - group = getgrgid(cgrldp->gid); - if (!group) { - dbg("getgrgid() failed for gid %d\n", - cgrldp->pw->pw_gid); - ret = ECGOTHER; - goto out; - } - if ((strcmp(group->gr_name, user+1) == 0) - || (strcmp(user, "*") == 0) || - (match_gid && !strcmp(user, "%"))) { - match_gid = 1; - - cgroup = (struct cgroup *) - calloc(1, sizeof(struct cgroup)); - if (!cgroup) { - ret = ECGOTHER; - goto out; - } - cgroups[cgindex] = cgroup; - cgindex++; - ret = cg_prepare_controller_array(buf_ctrl, controllers); - if (ret) - goto out; - ret = cg_prepare_cgroup(cgroup, - cgrldp->pid, - dest, controllers); - if (ret) - goto out; - cg_free_controller_array(controllers); - } + /* This is the simple case of the UID matching. */ + if (ret->uid == uid) { + goto finished; + } + + /* This is the simple case of the GID matching. */ + if (ret->gid == gid) { + goto finished; + } + + /* If this is a group rule, the UID might be a member. */ + if (ret->name[0] == '@') { + /* Get the group data. */ + sp = &(ret->name[1]); + grp = getgrnam(sp); + if (!grp) { + continue; + } + + /* Get the data for UID. */ + usr = getpwuid(uid); + if (!usr) { + continue; + } + + /* If UID is a member of group, we matched. */ + for (i = 0; grp->gr_mem[i]; i++) { + if (!(strcmp(usr->pw_name, grp->gr_mem[i]))) + goto finished; } - } else { - dbg("invalid line '%s' - skipped", line); } + + /* If we haven't matched, try the next rule. */ + ret = ret->next; } - /* If we are here, then none of the rule matched for the task */ - dbg("No rules matched for task with pid %d\n", cgrldp->pid); - fclose(fp); - return 0; -out: - fclose(fp); - cg_free_controller_array(controllers); - /* Free the cgroups allocated so far */ - for (i = 0; (i < CG_CONTROLLER_MAX) && cgroups[i]; i++) - cgroup_free(&cgroups[i]); + + /* If we get here, no rules matched. */ + ret = NULL; + +finished: + pthread_rwlock_unlock(&rl_lock); return ret; } -/** cgroup_change_cgroup_uid_gid changes the cgroup of a program based on - * rules in the config file. Rules are search based on uid and gid - * and the pid is placed into destination group (if permissions are - * there). +/** + * Changes the cgroup of a program based on the rules in the config file. If a + * rule exists for the given UID or GID, then the given PID is placed into the + * correct group. By default, this function parses the configuration file each + * time it is called. + * + * The flags can alter the behavior of this function: + * CGFLAG_USECACHE: Use cached rules instead of parsing the config file * - * returns 0 on success. + * This function may NOT be thread safe. + * @param uid The UID to match + * @param gid The GID to match + * @param pid The PID of the process to move + * @param flags Bit flags to change the behavior, as defined above + * @return 0 on success, > 0 on error + * TODO: Determine thread-safeness and fix of not safe. */ -int cgroup_change_cgroup_uid_gid(uid_t uid, gid_t gid, pid_t pid) +int cgroup_change_cgroup_uid_gid_flags(const uid_t uid, const gid_t gid, + const pid_t pid, const int flags) { - int ret = 0, i; - struct passwd *pw; - struct cgroup_rules_data cgrld, *cgrldp = &cgrld; - struct cgroup *cgroups[CG_HIER_MAX]; + /* Temporary pointer to a rule */ + struct cgroup_rule *tmp = NULL; + + /* Return codes */ + int ret = 0; + /* We need to check this before doing anything else! */ if (!cgroup_initialized) { dbg("libcgroup is not initialized\n"); - return ECGROUPNOTINITIALIZED; + ret = ECGROUPNOTINITIALIZED; + goto finished; } - memset(cgrldp, 0, sizeof(struct cgroup_rules_data)); - memset(cgroups, 0, CG_HIER_MAX); - pw = getpwuid(uid); - if (!pw) { - dbg("Could not retrieve the credentials of user" - "with uid %d\n", uid); - return ECGOTHER; - } - cgrldp->pw = pw; - cgrldp->pid = pid; - cgrldp->gid = gid; + /* + * If the user did not ask for cached rules, we must parse the + * configuration to find a matching rule (if one exists). Else, we'll + * find the first match in the cached list (rl). + */ + if (!(flags & CGFLAG_USECACHE)) { + dbg("Not using cached rules for PID %d.\n", pid); + ret = cgroup_parse_rules(false, uid, gid); + + /* The configuration file has an error! We must exit now. */ + if (ret != -1 && ret != 0) { + dbg("Failed to parse the configuration rules.\n"); + goto finished; + } - /* Parse config file */ - ret = cg_parse_rules_config_file(cgrldp, cgroups); - if (ret) { - dbg("Parsing of %s failed\n", CGRULES_CONF_FILE); - return ret; + /* We did not find a matching rule, so we're done. */ + if (ret == 0) { + dbg("No rule found to match PID: %d, UID: %d, " + "GID: %d\n", pid, uid, gid); + goto finished; + } + + /* Otherwise, we did match a rule and it's in trl. */ + tmp = trl.head; + } else { + /* Find the first matching rule in the cached list. */ + tmp = cgroup_find_matching_rule_uid_gid(uid, gid); + if (!tmp) { + dbg("No rule found to match PID: %d, UID: %d, " + "GID: %d\n", pid, uid, gid); + ret = 0; + goto finished; + } } + dbg("Found matching rule %s for PID: %d, UID: %d, GID: %d\n", + tmp->name, pid, uid, gid); - /* Add task to cgroups */ - for (i = 0; (i < CG_HIER_MAX) && cgroups[i]; i++) { - ret = cgroup_attach_task_pid(cgroups[i], cgrldp->pid); + /* If we are here, then we found a matching rule, so execute it. */ + do { + dbg("Executing rule %s for PID %d... ", tmp->name, pid); + ret = cgroup_change_cgroup_path(tmp->destination, + pid, tmp->controllers); if (ret) { - dbg("cgroup_attach_task_pid failed:%d\n", ret); - goto out; + dbg("FAILED! (Error Code: %d)\n", ret); + goto finished; } - } -out: - /* Free the cgroups */ - for (i = 0; (i < CG_HIER_MAX) && cgroups[i]; i++) - cgroup_free(&cgroups[i]); + dbg("OK!\n"); + + /* Now, check for multi-line rules. As long as the "next" + * rule starts with '%', it's actually part of the rule that + * we just executed. + */ + tmp = tmp->next; + } while (tmp && (tmp->name[0] == '%')); + +finished: return ret; } -/** cgroup_change_cgroup_path changes the cgroup of a program based on - * the path provided by user. In this case user already knows in which - * cgroup the task should go and no rules file have to be parsed. +/** + * Provides backwards-compatibility with older versions of the API. This + * function is deprecated, and cgroup_change_cgroup_uid_gid_flags() should be + * used instead. In fact, this function simply calls the newer one with flags + * set to 0 (none). + * @param uid The UID to match + * @param gid The GID to match + * @param pid The PID of the process to move + * @return 0 on success, > 0 on error + * + */ +int cgroup_change_cgroup_uid_gid(uid_t uid, gid_t gid, pid_t pid) +{ + return cgroup_change_cgroup_uid_gid_flags(uid, gid, pid, 0); +} + +/** + * Changes the cgroup of a program based on the path provided. In this case, + * the user must already know into which cgroup the task should be placed and + * no rules will be parsed. * * returns 0 on success. */ @@ -1460,3 +1807,104 @@ int cgroup_change_cgroup_path(char *dest, pid_t pid, char *controllers[]) } return 0; } + +/** + * Print the cached rules table. This function should be called only after + * first calling cgroup_parse_config(), but it will work with an empty rule + * list. + * @param fp The file stream to print to + */ +void cgroup_print_rules_config(FILE *fp) +{ + /* Iterator */ + struct cgroup_rule *itr; + + /* Loop variable */ + int i = 0; + + pthread_rwlock_rdlock(&rl_lock); + + if (!(rl.head)) { + fprintf(fp, "The rules table is empty.\n\n"); + pthread_rwlock_unlock(&rl_lock); + return; + } + + itr = rl.head; + while (itr) { + fprintf(fp, "Rule: %s\n", itr->name); + + if (itr->uid == CGRULE_WILD) + fprintf(fp, " UID: any\n"); + else if (itr->uid == CGRULE_INVALID) + fprintf(fp, " UID: N/A\n"); + else + fprintf(fp, " UID: %d\n", itr->uid); + + if (itr->gid == CGRULE_WILD) + fprintf(fp, " GID: any\n"); + else if (itr->gid == CGRULE_INVALID) + fprintf(fp, " GID: N/A\n"); + else + fprintf(fp, " GID: %d\n", itr->gid); + + fprintf(fp, " DEST: %s\n", itr->destination); + + fprintf(fp, " CONTROLLERS:\n"); + for (i = 0; i < MAX_MNT_ELEMENTS; i++) { + if (itr->controllers[i]) { + fprintf(fp, " %s\n", itr->controllers[i]); + } + } + fprintf(fp, "\n"); + itr = itr->next; + } + pthread_rwlock_unlock(&rl_lock); +} + +/** + * Reloads the rules list, using the given configuration file. This function + * is probably NOT thread safe (calls cgroup_parse_rules()). + * @return 0 on success, > 0 on failure + */ +int cgroup_reload_cached_rules() +{ + /* Return codes */ + int ret = 0; + + dbg("Reloading cached rules from %s.\n", CGRULES_CONF_FILE); + if ((ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID))) { + dbg("Error parsing configuration file \"%s\": %d.\n", + CGRULES_CONF_FILE, ret); + ret = ECGROUPPARSEFAIL; + goto finished; + } + + #ifdef DEBUG + cgroup_print_rules_config(stdout); + #endif + +finished: + return ret; +} + +/** + * Initializes the rules cache. + * @return 0 on success, > 0 on error + */ +int cgroup_init_rules_cache() +{ + /* Return codes */ + int ret = 0; + + /* Attempt to read the configuration file and cache the rules. */ + ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID); + if (ret) { + dbg("Could not initialize rule cache, error was: %d\n", ret); + cgroup_rules_loaded = false; + } else { + cgroup_rules_loaded = true; + } + + return ret; +} diff --git a/cgrulesengd.c b/cgrulesengd.c new file mode 100644 index 0000000..02d0cd5 --- /dev/null +++ b/cgrulesengd.c @@ -0,0 +1,625 @@ +/* + * Copyright Red Hat Inc. 2008 + * + * Author: Steve Olivieri + * Author: Vivek Goyal + * + * Some part of the programs have been derived from Dhaval Giani's posting + * for daemon to place the task in right container. Original copyright notice + * follows. + * + * Copyright IBM Corporation, 2007 + * Author: Dhaval Giani linux.vnet.ibm.com> + * Derived from test_cn_proc.c by Matt Helsley + * Original copyright notice follows + * + * Copyright (C) Matt Helsley, IBM Corp. 2005 + * Derived from fcctl.c by Guillaume Thouvenin + * Original copyright notice follows: + * + * Copyright (C) 2005 BULL SA. + * Written by Guillaume Thouvenin bull.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * TODO Stop using netlink for communication (or at least rewrite that part). + */ + +#include "libcgroup.h" +#include "cgrulesengd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Log file */ +FILE* logfile; + +/** + * Prints the usage information for this program and, optionally, an error + * message. This function uses vfprintf. + * @param fd The file stream to print to + * @param msg The error message to print (printf style) + * @param ... Any args to msg (printf style) + */ +void usage(FILE* fd, const char* msg, ...) +{ + /* List of args to msg */ + va_list ap; + + /* Put all args after msg into the list. */ + va_start(ap, msg); + + if (msg) + vfprintf(fd, msg, ap); + fprintf(fd, "\n"); + fprintf(fd, "cgrulesengd -- a daemon for the cgroups rules engine\n"); + fprintf(fd, " usage : cgrulesengd [--nodaemon] [--nolog] [--log FILE]" + "\n"); + va_end(ap); +} + +/** + * Prints a formatted message (like printf()) to a file stream, and flushes + * the file stream's buffer so that the message is immediately readable. + * @param fd The file stream to write to + * @param format The format for the message (printf style) + * @param ... Any args to format (printf style) + */ +void flog(FILE* fd, const char* format, ...) +{ + /* List of args to format */ + va_list ap; + + /* Print the message to the given stream. */ + va_start(ap, format); + vfprintf(fd, format, ap); + va_end(ap); + + /* Flush the stream's buffer, so the data is readable immediately. */ + fflush(fd); +} + +/** + * Process an event from the kernel, and determine the correct UID/GID/PID to + * pass to libcgroup. Then, libcgroup will decide the cgroup to move the PID + * to, if any. + * @param ev The event to process + * @param type The type of event to process (part of ev) + * @return 0 on success, > 0 on failure + */ +int cgre_process_event(const struct proc_event *ev, const int type) +{ + /* Handle for the /proc/PID/status file */ + FILE *f; + + /* Path for /proc/PID/status file */ + char path[FILENAME_MAX]; + + /* Temporary buffer */ + char *buf = NULL; + + /* UID data */ + uid_t ruid, euid, suid, fsuid; + + /* GID data */ + gid_t rgid, egid, sgid, fsgid; + + /* Return codes */ + int ret = 0; + + /* + * First, we need to open the /proc/PID/status file so that we can + * get the effective UID and GID for the process that we're working + * on. This process is probably not us, so we can't just call + * geteuid() or getegid(). + */ + sprintf(path, "/proc/%d/status", ev->event_data.id.process_pid); + f = fopen(path, "r"); + if (!f) { + flog(logfile, "Failed to open %s", path); + goto finished; + } + + /* Now, we need to find either the eUID or the eGID of the process. */ + buf = calloc(4096, sizeof(char)); + if (!buf) { + flog(logfile, "Failed to process event, out of" + "memory? Error: %s\n", + strerror(errno)); + ret = errno; + fclose(f); + goto finished; + } + switch (type) { + case PROC_EVENT_UID: + /* Have the eUID, need to find the eGID. */ + while (fgets(buf, 4096, f)) { + if (!strncmp(buf, "Gid:", 4)) { + sscanf((buf + 5), "%d%d%d%d", &rgid, &egid, + &sgid, &fsgid); + break; + } + memset(buf, '\0', 4096); + } + break; + case PROC_EVENT_GID: + /* Have the eGID, need to find the eUID. */ + while (fgets(buf, 4096, f)) { + if (!strncmp(buf, "Uid:", 4)) { + sscanf((buf + 5), "%d%d%d%d", &ruid, &euid, + &suid, &fsuid); + break; + } + memset(buf, '\0', 4096); + } + break; + default: + flog(logfile, "For some reason, we're processing a non-UID/GID" + " event. Something is wrong!\n"); + break; + } + free(buf); + fclose(f); + + /* + * Now that we have the UID, the GID, and the PID, we can make a call + * to libcgroup to change the cgroup for this PID. + */ + switch (type) { + case PROC_EVENT_UID: + flog(logfile, "Attempting to change cgroup for PID: %d, " + "UID: %d, GID: %d... ", + ev->event_data.id.process_pid, + ev->event_data.id.e.euid, egid); + ret = cgroup_change_cgroup_uid_gid_flags( + ev->event_data.id.e.euid, + egid, ev->event_data.id.process_pid, + CGFLAG_USECACHE); + break; + case PROC_EVENT_GID: + flog(logfile, "Attempting to change cgroup for PID: %d, " + "UID: %d, GID: %d... ", + ev->event_data.id.process_pid, euid, + ev->event_data.id.e.egid); + ret = cgroup_change_cgroup_uid_gid_flags(euid, + ev->event_data.id.e.egid, + ev->event_data.id.process_pid, + CGFLAG_USECACHE); + break; + default: + break; + } + + if (ret) { + flog(logfile, "FAILED!\n (Error Code: %d)\n", ret); + } else { + flog(logfile, "OK!\n"); + } + +finished: + return ret; +} + +/** + * Handle a netlink message. In the event of PROC_EVENT_UID or PROC_EVENT_GID, + * we pass the event along to cgre_process_event for further processing. All + * other events are ignored. + * @param cn_hdr The netlink message + * @return 0 on success, > 0 on error + */ +int cgre_handle_msg(struct cn_msg *cn_hdr) +{ + /* The event to consider */ + struct proc_event *ev; + + /* Return codes */ + int ret = 0; + + /* Get the event data. We only care about two event types. */ + ev = (struct proc_event*)cn_hdr->data; + switch (ev->what) { + case PROC_EVENT_UID: + flog(logfile, "UID Event:\n"); + flog(logfile, " PID = %d, tGID = %d, rUID = %d, eUID = %d\n", + ev->event_data.id.process_pid, + ev->event_data.id.process_tgid, + ev->event_data.id.r.ruid, + ev->event_data.id.e.euid); + ret = cgre_process_event(ev, PROC_EVENT_UID); + break; + case PROC_EVENT_GID: + flog(logfile, "GID Event:\n"); + flog(logfile, " PID = %d, tGID = %d, rGID = %d, eGID = %d\n", + ev->event_data.id.process_pid, + ev->event_data.id.process_tgid, + ev->event_data.id.r.rgid, + ev->event_data.id.e.egid); + ret = cgre_process_event(ev, PROC_EVENT_GID); + break; + default: + break; + } + + return ret; +} + +int cgre_create_netlink_socket_process_msg() +{ + int sk_nl; + int err; + struct sockaddr_nl my_nla, kern_nla, from_nla; + socklen_t from_nla_len; + char buff[BUFF_SIZE]; + int rc = -1; + struct nlmsghdr *nl_hdr; + struct cn_msg *cn_hdr; + enum proc_cn_mcast_op *mcop_msg; + size_t recv_len = 0; + + /* + * Create an endpoint for communication. Use the kernel user + * interface device (PF_NETLINK) which is a datagram oriented + * service (SOCK_DGRAM). The protocol used is the connector + * protocol (NETLINK_CONNECTOR) + */ + sk_nl = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); + if (sk_nl == -1) { + printf("socket sk_nl error"); + return rc; + } + + my_nla.nl_family = AF_NETLINK; + my_nla.nl_groups = CN_IDX_PROC; + my_nla.nl_pid = getpid(); + my_nla.nl_pad = 0; + + kern_nla.nl_family = AF_NETLINK; + kern_nla.nl_groups = CN_IDX_PROC; + kern_nla.nl_pid = 1; + kern_nla.nl_pad = 0; + + err = bind(sk_nl, (struct sockaddr *)&my_nla, sizeof(my_nla)); + if (err == -1) { + printf("binding sk_nl error"); + goto close_and_exit; + } + + nl_hdr = (struct nlmsghdr *)buff; + cn_hdr = (struct cn_msg *)NLMSG_DATA(nl_hdr); + mcop_msg = (enum proc_cn_mcast_op*)&cn_hdr->data[0]; + printf("sending proc connector: PROC_CN_MCAST_LISTEN... "); + memset(buff, 0, sizeof(buff)); + *mcop_msg = PROC_CN_MCAST_LISTEN; + + /* fill the netlink header */ + nl_hdr->nlmsg_len = SEND_MESSAGE_LEN; + nl_hdr->nlmsg_type = NLMSG_DONE; + nl_hdr->nlmsg_flags = 0; + nl_hdr->nlmsg_seq = 0; + nl_hdr->nlmsg_pid = getpid(); + + /* fill the connector header */ + cn_hdr->id.idx = CN_IDX_PROC; + cn_hdr->id.val = CN_VAL_PROC; + cn_hdr->seq = 0; + cn_hdr->ack = 0; + cn_hdr->len = sizeof(enum proc_cn_mcast_op); + printf("sending netlink message len=%d, cn_msg len=%d\n", + nl_hdr->nlmsg_len, sizeof(struct cn_msg)); + if (send(sk_nl, nl_hdr, nl_hdr->nlmsg_len, 0) != nl_hdr->nlmsg_len) { + printf("failed to send proc connector mcast ctl op!\n"); + goto close_and_exit; + } + printf("sent\n"); + + for(memset(buff, 0, sizeof(buff)), from_nla_len = sizeof(from_nla); + ; memset(buff, 0, sizeof(buff)), from_nla_len = sizeof(from_nla)) { + struct nlmsghdr *nlh = (struct nlmsghdr*)buff; + memcpy(&from_nla, &kern_nla, sizeof(from_nla)); + recv_len = recvfrom(sk_nl, buff, BUFF_SIZE, 0, + (struct sockaddr*)&from_nla, &from_nla_len); + if (recv_len == ENOBUFS) { + flog(logfile, "************************************" + "***********\n" + "!***ERROR: NETLINK BUFFER FULL, MSG " + "DROPPED***!\n" + "************************************" + "***********\n"); + continue; + } + if (recv_len < 1) + continue; + while (NLMSG_OK(nlh, recv_len)) { + cn_hdr = NLMSG_DATA(nlh); + if (nlh->nlmsg_type == NLMSG_NOOP) + continue; + if ((nlh->nlmsg_type == NLMSG_ERROR) || + (nlh->nlmsg_type == NLMSG_OVERRUN)) + break; + if(cgre_handle_msg(cn_hdr) < 0) { + goto close_and_exit; + } + if (nlh->nlmsg_type == NLMSG_DONE) + break; + nlh = NLMSG_NEXT(nlh, recv_len); + } + } + +close_and_exit: + close(sk_nl); + return rc; +} + +/** + * Turns this program into a daemon. In doing so, we fork() and kill the + * parent process. Note too that stdout, stdin, and stderr are closed in + * daemon mode, and a file descriptor for a log file is opened. + * @param logp Path of the log file + * @param daemon False to turn off daemon mode (no fork, leave FDs open) + * @param logs False to disable logging (no log FD, leave stdout open) + * @return 0 on success, > 0 on error + */ +int cgre_start_daemon(const char* logp, const unsigned char daemon, + const unsigned char logs) +{ + /* PID returned from the fork() */ + pid_t pid; + + /* Current system time */ + time_t tm; + + /* Fork and die. */ + if (daemon) { + pid = fork(); + if (pid < 0) { + openlog("CGRE", LOG_CONS, LOG_DAEMON|LOG_WARNING); + syslog(LOG_DAEMON|LOG_WARNING, "Failed to fork," + " error: %s", strerror(errno)); + closelog(); + flog(stderr, "Failed to fork(), %s\n", strerror(errno)); + return 1; + } else if (pid > 0) { + flog(stdout, "Starting in daemon mode.\n"); + exit(EXIT_SUCCESS); + } + + /* Change the file mode mask. */ + umask(0); + } else { + dbg("Not using daemon mode.\n"); + pid = getpid(); + } + + if (logs) { + logfile = fopen(logp, "a"); + if (!logfile) { + flog(stderr, "Failed to open log file %s, error: %s." + " Continuing anyway.\n", logp, + strerror(errno)); + logfile = stdout; + } else { + flog(logfile, "CGroup Rules Engine Daemon\n"); + tm = time(0); + flog(logfile, "Current time: %s", ctime(&tm)); + flog(stdout, "Opened log file: %s\n", logp); + } + } else { + logfile = stdout; + flog(stdout, "Proceeding with stdout as log output.\n"); + } + + if (!daemon) { + /* We can skip the rest, since we're not becoming a daemon. */ + flog(logfile, "Proceeding with PID %d\n\n", getpid()); + if (logfile != stdout) + flog(stdout, "Proceeding with PID %d\n", getpid()); + return 0; + } else { + /* Get a new SID for the child. */ + if (setsid() < 0) { + flog(logfile, "Failed to get a new SID, error: %s\n", + strerror(errno)); + return 2; + } + + /* Change to the root directory. */ + if (chdir("/") < 0) { + flog(logfile, "Failed to chdir to /, error: %s\n", + strerror(errno)); + return 3; + } + + /* Close standard file descriptors. */ + close(STDIN_FILENO); + if (logfile != stdout) + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + + /* If we make it this far, we're a real daemon! Or we chose not to. */ + flog(logfile, "Proceeding with PID %d\n\n", getpid()); + return 0; +} + +/** + * Catch the SIGUSR2 signal and reload the rules configuration. This function + * makes use of the logfile and flog() to print the new rules. + * @param signum The signal that we caught (always SIGUSR2) + */ +void cgre_flash_rules(int signum) +{ + /* Current time */ + time_t tm = time(0); + + flog(logfile, "\nReloading rules configuration.\n"); + flog(logfile, "Current time: %s\n", ctime(&tm)); + + /* Ask libcgroup to reload the rules table. */ + cgroup_reload_cached_rules(); + + /* Print the results of the new table to our log file. */ + cgroup_print_rules_config(logfile); + flog(logfile, "\n"); +} + +/** + * Catch the SIGTERM and SIGINT signals so that we can exit gracefully. Before + * exiting, this function makes use of the logfile and flog(). + * @param signum The signal that we caught (SIGTERM, SIGINT) + */ +void cgre_catch_term(int signum) +{ + /* Current time */ + time_t tm = time(0); + + flog(logfile, "\nStopped CGroup Rules Engine Daemon at %s", + ctime(&tm)); + flog(logfile, "========================================"); + flog(logfile, "========================================\n\n"); + + /* Close the log file, if we opened one. */ + if (logfile && logfile != stdout) + fclose(logfile); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + /* Patch to the log file */ + char logp[FILENAME_MAX]; + + /* For catching signals */ + struct sigaction sa; + + /* Should we daemonize? */ + unsigned char daemon = 1; + + /* Should we log? */ + unsigned char logs = 1; + + /* Return codes */ + int ret = 0; + + /* Loop variable */ + int i = 0; + + /* Make sure the user is root. */ + if (getuid() != 0) { + fprintf(stderr, "Error: Only root can start/stop the control" + " group rules engine daemon\n"); + ret = 1; + goto finished; + } + + /* Set the default log file. */ + memset(logp, '\0', FILENAME_MAX); + strncpy(logp, "/root/cgrulesengd.log", + strlen("/root/cgrulesengd.log")); + logfile = NULL; + + /* Parse user args. */ + for (i = 1; i < argc; i++) { + if (strncmp(argv[i], "--log", strlen("--log")) == 0) { + i++; + memset(logp, '\0', FILENAME_MAX); + strncpy(logp, argv[i], strlen(argv[i])); + continue; + } + if (strncmp(argv[i], "--nodaemon", strlen("--nodaemon")) == 0) { + daemon = 0; + continue; + } + if (strncmp(argv[i], "--nolog", strlen("--nolog")) == 0) { + logs = 0; + continue; + } + + /* If we get here, the user specified an invalid arg. */ + usage(stderr, "Invalid argument: %s", argv[i]); + ret = 2; + goto finished; + } + + flog(stdout, "Log file is: %s\n", logp); + + /* Initialize libcgroup. */ + if ((ret = cgroup_init()) != 0) { + fprintf(stderr, "Error: libcgroup initialization failed, %d\n", + ret); + goto finished; + } + + /* Ask libcgroup to load the configuration rules. */ + if ((ret = cgroup_init_rules_cache()) != 0) { + fprintf(stderr, "Error: libcgroup failed to initialize rules" + "cache, %d\n", ret); + goto finished; + } + + /* Now, start the daemon. */ + if ((ret = cgre_start_daemon(logp, daemon, logs)) < 0) { + fprintf(stderr, "Error: Failed to launch the daemon, %d\n", + ret); + goto finished; + } + + /* + * Set up the signal handler to reload the cached rules upon reception + * of a SIGUSR2 signal. + */ + sa.sa_handler = &cgre_flash_rules; + sa.sa_flags = 0; + sa.sa_restorer = NULL; + sigemptyset(&sa.sa_mask); + if ((ret = sigaction(SIGUSR2, &sa, NULL))) { + flog(logfile, "Failed to set up signal handler for SIGUSR2." + " Error: %s\n", strerror(errno)); + goto finished; + } + + /* + * Set up the signal handler to catch SIGINT and SIGTERM so that we + * can exit gracefully. + */ + sa.sa_handler = &cgre_catch_term; + ret = sigaction(SIGINT, &sa, NULL); + ret |= sigaction(SIGTERM, &sa, NULL); + if (ret) { + flog(logfile, "Failed to set up the signal handler. Error:" + " %s\n", strerror(errno)); + goto finished; + } + + /* Print the configuration to the log file, or stdout. */ + cgroup_print_rules_config(logfile); + flog(logfile, "Started the CGroup Rules Engine Daemon.\n"); + + /* We loop endlesly in this function, unless we encounter an error. */ + ret = cgre_create_netlink_socket_process_msg(); + +finished: + if (logfile && logfile != stdout) + fclose(logfile); + + return ret; +} diff --git a/cgrulesengd.h b/cgrulesengd.h new file mode 100644 index 0000000..bdffd31 --- /dev/null +++ b/cgrulesengd.h @@ -0,0 +1,124 @@ +/* + * Copyright Red Hat Inc. 2008 + * + * Author: Steve Olivieri + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef _CGRULESENGD_H +#define _CGRULESENGD_H + +#include + +__BEGIN_DECLS + +#include "libcgroup.h" +#include + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef __USE_GNU +#define __USE_GNU +#endif + +/* A simple macro for printing messages only when DEBUG is defined. */ +#ifdef DEBUG + #define fdbg(a, b...) fprintf(a, b) +#else + #define fdbg(a, b...) do {} while(0) +#endif /* DEBUG */ + +/* The following ten macros are all for the Netlink code. */ +#define SEND_MESSAGE_LEN (NLMSG_LENGTH(sizeof(struct cn_msg) + \ + sizeof(enum proc_cn_mcast_op))) +#define RECV_MESSAGE_LEN (NLMSG_LENGTH(sizeof(struct cn_msg) + \ + sizeof(struct proc_event))) + +#define SEND_MESSAGE_SIZE (NLMSG_SPACE(SEND_MESSAGE_LEN)) +#define RECV_MESSAGE_SIZE (NLMSG_SPACE(RECV_MESSAGE_LEN)) + +#define max(x,y) ((y)<(x)?(x):(y)) +#define min(x,y) ((y)>(x)?(x):(y)) + +#define BUFF_SIZE (max(max(SEND_MESSAGE_SIZE, RECV_MESSAGE_SIZE), 1024)) +#define MIN_RECV_SIZE (min(SEND_MESSAGE_SIZE, RECV_MESSAGE_SIZE)) + +#define PROC_CN_MCAST_LISTEN (1) +#define PROC_CN_MCAST_IGNORE (2) + +/** + * Prints the usage information for this program and, optionally, an error + * message. This function uses vfprintf. + * @param fd The file stream to print to + * @param msg The error message to print (printf style) + * @param ... Any args to msg (printf style) + */ +void cgre_usage(FILE *fd, const char *msg, ...); + +/** + * Prints a formatted message (like printf()) to a file stream, and flushes + * the file stream's buffer so that the message is immediately readable. + * @param fd The file stream to write to + * @param format The format for the message (printf style) + * @param ... Any args to format (printf style) + */ +void flog(FILE* fd, const char* msg, ...); + +/** + * Process an event from the kernel, and determine the correct UID/GID/PID to + * pass to libcgroup. Then, libcgroup will decide the cgroup to move the PID + * to, if any. + * @param ev The event to process + * @param type The type of event to process (part of ev) + * @return 0 on success, > 0 on failure + */ +int cgre_process_event(const struct proc_event *ev, const int type); + +/** + * Handle a netlink message. In the event of PROC_EVENT_UID or PROC_EVENT_GID, + * we pass the event along to cgre_process_event for further processing. All + * other events are ignored. + * @param cn_hdr The netlink message + * @return 0 on success, > 0 on error + */ +int cgre_handle_message(struct cn_msg *cn_hdr); + +/** + * Turns this program into a daemon. In doing so, we fork() and kill the + * parent process. Note too that stdout, stdin, and stderr are closed in + * daemon mode, and a file descriptor for a log file is opened. + * @param logp Path of the log file + * @param daemon False to turn off daemon mode (no fork, leave FDs open) + * @param logs False to disable logging (no log FD, leave stdout open) + * @return 0 on success, > 0 on error + */ +int cgre_start_daemon(const char *logp, const unsigned char daemon, + const unsigned char logs); + +/** + * Catch the SIGUSR2 signal and reload the rules configuration. This function + * makes use of the logfile and flog() to print the new rules. + * @param signum The signal that we caught (always SIGUSR2) + */ +void cgre_flash_rules(int signum); + +/** + * Catch the SIGTERM and SIGINT signal so that we can exit gracefully. Before + * exiting, this function makes use of the logfile and flog(). + * @param signum The signal that we caught (SIGTERM, SIGINT) + */ +void cgre_catch_term(int signum); + +__END_DECLS + +#endif /* _CGRULESENGD_H */ + diff --git a/libcgroup-internal.h b/libcgroup-internal.h index f422cc5..c2f2ce3 100644 --- a/libcgroup-internal.h +++ b/libcgroup-internal.h @@ -60,6 +60,24 @@ struct cgroup_rules_data { gid_t gid; }; +/* A rule that maps UID/GID to a cgroup */ +struct cgroup_rule { + uid_t uid; + gid_t gid; + char name[LOGIN_NAME_MAX]; + char destination[FILENAME_MAX]; + char *controllers[MAX_MNT_ELEMENTS]; + struct cgroup_rule *next; +}; + +/* Container for a list of rules */ +struct cgroup_rule_list { + struct cgroup_rule *head; + struct cgroup_rule *tail; + int len; +}; + + __END_DECLS #endif diff --git a/libcgroup.h b/libcgroup.h index dc7e23f..c9e9689 100644 --- a/libcgroup.h +++ b/libcgroup.h @@ -27,6 +27,8 @@ __BEGIN_DECLS #include #include #include +#include +#include #ifndef _GNU_SOURCE #define _GNU_SOURCE @@ -94,6 +96,19 @@ struct list_of_names { struct list_of_names *next; }; +/* Maximum length of a line in the daemon config file */ +#define CGROUP_RULE_MAXLINE (FILENAME_MAX + LOGIN_NAME_MAX + \ + CG_CONTROLLER_MAX + 3) + +/* Definitions for the uid and gid members of a cgroup_rules */ +#define CGRULE_INVALID (-1) +#define CGRULE_WILD (-2) + +/* Flags for cgroup_change_cgroup_uid_gid() */ +enum cgflags { + CGFLAG_USECACHE = 0x01, +}; + enum cg_msg_type { CG_MSG_LOAD_FILE, CG_MSG_UNLOAD_FILE, @@ -124,6 +139,8 @@ enum cgroup_errors { ECGOTHER, ECGROUPNOTEQUAL, ECGCONTROLLERNOTEQUAL, + ECGROUPPARSEFAIL, /* Failed to parse rules configuration file. */ + ECGROUPNORULES, /* Rules list does not exist. */ }; #define CG_MAX_MSG_SIZE 256 @@ -175,10 +192,70 @@ struct cgroup *cgroup_get_cgroup(struct cgroup *cgroup); int cgroup_create_cgroup_from_parent(struct cgroup *cgroup, int ignore_ownership); int cgroup_copy_cgroup(struct cgroup *dst, struct cgroup *src); -/* Changes the cgroup of calling application based on rule file */ +/** + * Changes the cgroup of a program based on the rules in the config file. If a + * rule exists for the given UID or GID, then the given PID is placed into the + * correct group. By default, this function parses the configuration file each + * time it is called. + * + * The flags can alter the behavior of this function: + * CGFLAG_USECACHE: Use cached rules instead of parsing the config file + * + * This function may NOT be thread safe. + * @param uid The UID to match + * @param gid The GID to match + * @param pid The PID of the process to move + * @param flags Bit flags to change the behavior, as defined above + * @return 0 on success, > 0 on error + * TODO: Determine thread-safeness and fix if not safe. + */ +int cgroup_change_cgroup_uid_gid_flags(const uid_t uid, const gid_t gid, + const pid_t pid, const int flags); + +/** + * Provides backwards-compatibility with older versions of the API. This + * function is deprecated, and cgroup_change_cgroup_uid_gid_flags() should be + * used instead. In fact, this function simply calls the newer one with flags + * set to 0 (none). + * @param uid The UID to match + * @param gid The GID to match + * @param pid The PID of the process to move + * @return 0 on success, > 0 on error + * + */ int cgroup_change_cgroup_uid_gid(uid_t uid, gid_t gid, pid_t pid); + +/** + * Changes the cgroup of a program based on the path provided. In this case, + * the user must already know into which cgroup the task should be placed and + * no rules will be parsed. + * + * returns 0 on success. + */ int cgroup_change_cgroup_path(char *path, pid_t pid, char *controllers[]); +/** + * Print the cached rules table. This function should be called only after + * first calling cgroup_parse_config(), but it will work with an empty rule + * list. + * @param fp The file stream to print to + */ +void cgroup_print_rules_config(FILE *fp); + +/** + * Reloads the rules list, using the given configuration file. This function + * is probably NOT thread safe (calls cgroup_parse_rules_config()). + * @return 0 on success, > 0 on failure + */ +int cgroup_reload_cached_rules(void); + +/** + * Initializes the rules cache. + * @return 0 on success, > 0 on failure + */ +int cgroup_init_rules_cache(void); + + /* The wrappers for filling libcg structures */ struct cgroup *cgroup_new_cgroup(const char *name); diff --git a/samples/cgred.conf b/samples/cgred.conf new file mode 100644 index 0000000..5363be5 --- /dev/null +++ b/samples/cgred.conf @@ -0,0 +1,19 @@ +# /etc/cgred.d/cgred.conf - CGroup Rules Engine Daemon configuration file +# +# The four options listed below (CONFIG_FILE, LOG_FILE, NODAEMON, NOLOG) are +# the only valid ones. Defining anything else in this file will cause the +# CGroup Rules Engine program to fail. So, don't do it. + +# The pathname to the configuration file for CGroup Rules Engine +CONFIG_FILE="/etc/cgrules.conf" + +# The pathname to the log file for CGroup Rules Engine +LOG_FILE="/root/cgrulesengd.log" + +# Uncomment the second line to run CGroup Rules Engine in non-daemon mode +NODAEMON="" +#NODAEMON="--nodaemon" + +# Uncomment the second line to disable logging for CGroup Rules Engine +NOLOG="" +#NOLOG="--nolog" diff --git a/scripts/init.d/cgred b/scripts/init.d/cgred new file mode 100644 index 0000000..b99e769 --- /dev/null +++ b/scripts/init.d/cgred @@ -0,0 +1,129 @@ +#!/bin/bash +# +# Start/Stop the CGroups Rules Engine Daemon +# +# Copyright Red Hat Inc. 2008 +# +# Authors: Steve Olivieri +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# cgred GGroups Rules Engine Daemon +# chkconfig: 2345 80 20 +# description: This is a daemon for automatically classifying processes \ +# into cgroups based on UID/GID. +# +# processname: cgrulesengd +# pidfile: /var/run/cgred.pid +# +### BEGIN INIT INFO +# Provides: cgrulesengd +# Required-Start: $local_fs $syslog $wlm +# Required-Stop: $local_fs $syslog +# Should-Start: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop the cgroups rules engine daemon +# Description: CGroup Rules Engine is a tool for automatically using \ +# cgroups to classify processes +### END INIT INFO + +prefix=/usr +exec_prefix=/usr +bindir=/bin + +CGRED_BIN=${bindir}/cgrulesengd + +# Sanity checks +[ -x $CGRED_BIN ] || exit 1 +[ -x /lib/libcgroup.so ] || exit 1 + +# Source function library & LSB routines +. /etc/rc.d/init.d/functions +. /lib/lsb/init-functions + +# Read in configuration options. +if [ -f "/etc/cgred.d/cgred.conf" ] ; then + . /etc/cgred.d/cgred.conf + OPTIONS="--config $CONFIG_FILE --log $LOG_FILE $NODAEMON $NOLOG" +else + OPTIONS="" +fi + +# For convenience +processname=cgrulesengd +servicename=cgred +pidfile=/var/run/cgred.pid + +RETVAL=0 + +start() +{ + echo $"Starting CGroup Rules Engine Daemon..." + if [ -f "/var/lock/subsys/$servicename" ] ; then + echo "$servicename is already running with PID `cat ${pidfile}`" + return 1 + fi + daemon --check $servicename --pidfile $pidfile $processname $OPTIONS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$servicename + echo "`pidof $processname`" > $pidfile +} + +stop() +{ + echo -n $"Stopping CGroup Rules Engine Daemon..." + killproc -p $pidfile $processname -12 + RETVAL=$? + echo + if [ $RETVAL -eq 0 ] ; then + rm -f /var/lock/subsys/$servicename + rm -f $pidfile + fi +} + +# See how we are called +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status -p $pidfile $processname + RETVAL=$? + ;; + restart) + stop + start + ;; + condrestart) + if [ -f /var/lock/subsys/$servicename ] ; then + stop + start + fi + ;; + flash) + if [ -f /var/lock/subsys/$servicename ] ; then + echo $"Reloading rules configuration..." + kill -s 12 `cat ${pidfile}` + RETVAL=$? + echo + else + echo $"$servicename is not running." + fi + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|flash}" + ;; +esac + +exit $RETVAL diff --git a/tests/Makefile b/tests/Makefile index e668032..7a38b7a 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -2,9 +2,11 @@ LDFLAGS = -L .. LIBS = -lcgroup -lpthread INC = -I .. CXXFLAGS = -g -O2 -Wall -DDEBUG $(INC) +CFLAGS = -g -O2 -Wall -DDEBUG TARGET= libcgrouptest01 \ - libcg_ba + libcg_ba \ + setuid all: $(TARGET) @@ -14,5 +16,8 @@ libcgrouptest01: libcgrouptest01.c libcg_ba: libcg_ba.cpp $(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS) $(LIBS) +setuid: setuid.c + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LIBS) + clean: \rm -f $(TARGET) diff --git a/tests/setuid.c b/tests/setuid.c new file mode 100644 index 0000000..9d77850 --- /dev/null +++ b/tests/setuid.c @@ -0,0 +1,75 @@ +/* + * Copyright Red Hat Inc. 2008 + * + * Author: Steve Olivieri + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This is just a simple program for changing a UID or a GID. Comment out + * whichever block you don't want to use. + */ +int main(int argc, char *argv[]) +{ + /* User data */ + struct passwd *pwd; + + /* UID of user */ + uid_t uid; + + /* Group data */ + struct group *grp; + + /* GID of group */ + gid_t gid; + + /* Return codes */ + int ret; + + pwd = getpwnam(argv[1]); + uid = pwd->pw_uid; + fprintf(stdout, "Setting UID to %s (%d).\n", pwd->pw_name, uid); + if ((ret = setuid(uid))) { + fprintf(stderr, "Call to setuid() failed with error: %s\n", + strerror(errno)); + ret = -errno; + goto finished; + } + +// while(1) { +// grp = getgrnam("root"); +// gid = grp->gr_gid; +// fprintf(stdout, "Setting GID to %s (%d).\n", +// grp->gr_name, gid); +// if ((ret = setgid(gid))) { +// fprintf(stderr, "Call to setgid() failed with error:" +// " %s\n", strerror(errno)); +// ret = -errno; +// goto finished; +// } +// } + + while (1) { + usleep(3000000); + } + +finished: + return ret; +} -- cgit