module_helper.c
1 // Copyright 2006-2010, various authors 2 // 3 // This program is free software; you can redistribute it and/or modify 4 // it under the terms of the GNU General Public License as published by 5 // the Free Software Foundation; either version 2 of the License, or 6 // (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU General Public License for more details. 12 // 13 // You should have received a copy of the GNU General Public License 14 // along with this program; if not, write to the Free Software 15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 17 /* 18 This module_helper program will be installed setuid and allows 19 the user to add and remove a whitelist of modules necessary to 20 run EMC. Without a scheme like this, we have to rely on sudo 21 to start AND EXIT our program, and that may require the user to 22 enter a password. Prompting for a password to exit a program 23 is bad. If the user cancels at that phase of the run, it's also 24 bad since we leave realtime modules inserted and he'll probably 25 end up being forced to reboot. 26 27 In summary, I don't like this any more than you do, but I can't 28 think of a better way. 29 */ 30 31 #include <limits.h> 32 #include <stdlib.h> 33 #include <stdio.h> 34 #include <string.h> 35 #include <unistd.h> 36 #include <sys/utsname.h> 37 38 #include "config.h" 39 40 /* module name, between last / and ., must be one of these */ 41 /* if one module name is a prefix of the other (e.g., "rtai" is a prefix to 42 * "rtai_math") then put the shorter name last. 43 */ 44 char *module_whitelist[] = { 45 "rtai_math", "rtai_sem", "rtai_fifos", "rtai_up", "rtai_lxrt", 46 "rtai_hal", "rtai_sched", "rtai_smi", "rtai", "rt_mem_mgr", "adeos", 47 48 NULL 49 }; 50 51 /* module path must start with this. */ 52 53 char *path_whitelist[] = { 54 "/lib/modules", RTDIR, NULL, 55 56 NULL 57 }; 58 59 /* module extension must be one of these */ 60 61 char *ext_whitelist[] = { 62 ".ko", NULL 63 }; 64 65 void error(int argc, char **argv) { 66 int i; 67 int res; 68 char *prog = argv[0]; 69 70 /* drop root privs permanently */ 71 res = setuid(getuid()); 72 if(res != 0) 73 { 74 perror("setuid"); 75 exit(1); 76 } 77 78 fprintf(stderr, "%s: Invalid usage with args:", argv[0]); 79 for(i=1; i<argc; i++) { 80 fprintf(stderr, " %s", argv[i]); 81 } 82 fprintf(stderr, "\n\nUsage: %s insert /path/to/module.ext [param1=value1 ...]\n", prog); 83 fprintf(stderr, "\nwhere module is one of:\n"); 84 for(i=0; module_whitelist[i]; i++) { 85 fprintf(stderr, "\t%s\n", module_whitelist[i]); 86 } 87 fprintf(stderr, "\nthe path starts with one of:\n"); 88 for(i=0; path_whitelist[i]; i++) { 89 fprintf(stderr, "\t%s\n", path_whitelist[i]); 90 } 91 fprintf(stderr, "\nand the extension is one of:\n"); 92 for(i=0; ext_whitelist[i]; i++) { 93 fprintf(stderr, "\t%s\n", ext_whitelist[i]); 94 } 95 fprintf(stderr, "\nor the module is in the directory %s\n", 96 EMC2_RTLIB_DIR); 97 fprintf(stderr, "\nOR\n\n%s remove module\n\nwhere module is one of" 98 " the modules listed above.\n\n", prog); 99 exit(1); 100 } 101 102 /* Check that the next characters in target match one of the items 103 * in table. Failure is indicated by NULL, success is indicated by returning 104 * a pointer to just after the end of the string matched from the whitelist. 105 * If target was NULL, then the result is NULL. 106 * 107 * This means that it is easy to check for what comes after the whitelisted 108 * characters (e.g., module extension) even when the table entries have 109 * different lengths. 110 */ 111 char *check_whitelist(char *target, char *table[]) { 112 int i; 113 if(!target) return 0; 114 for(i=0; table[i]; i++) { 115 int sz = strlen(table[i]); 116 if(!strncmp(target, table[i], sz)) return target + sz; 117 } 118 return 0; 119 } 120 121 /* Check that the given module is in the whitelist. It's in the whitelist 122 * if it's inside the EMC2_RTLIB_DIR, or if it's in a directory 123 * whose prefix is path_whitelist and the module name is in the module_whitelist 124 * 125 * Paths without slashes and paths with ".." (even /a..b/) are never allowed. 126 * 127 * Paths must always start with a slash, but this is not explicitly tested. 128 * It's tested by strncmp() or check_whitelist() implicitly. 129 */ 130 void check_whitelist_module_path(char *mod, int argc, char **argv) { 131 char *ext, *end; 132 char *last_slash = strrchr(mod, '/'); 133 134 if(!last_slash || strstr(mod, "..")) error(argc, argv); 135 136 if(strncmp(mod, EMC2_RTLIB_DIR, strlen(EMC2_RTLIB_DIR)) == 0) 137 return; 138 139 ext = check_whitelist(last_slash + 1, module_whitelist); 140 if(!ext) error(argc, argv); 141 142 end = check_whitelist(ext, ext_whitelist); 143 if(!end || *end) error(argc, argv); 144 145 if(!check_whitelist(mod, path_whitelist)) error(argc, argv); 146 } 147 148 #include <sys/types.h> 149 #include <dirent.h> 150 151 /* Check that a module (without path or extension) is in the whitelist. 152 * It's in the whitelist if it's in the module_whitelist, or if it exists 153 * in the EMC2_RTLIB_DIR with the extension MODULE_EXT. 154 */ 155 void check_whitelist_module(char *mod, int argc, char **argv) { 156 char *end; 157 DIR *d = opendir(EMC2_RTLIB_DIR); 158 159 if(d) { 160 char buf[NAME_MAX + 1]; 161 snprintf(buf, NAME_MAX, "%s%s", mod, MODULE_EXT); 162 163 while(1) { 164 struct dirent *ent = readdir(d); 165 if(!ent) break; 166 if(strcmp(ent->d_name, buf) == 0) { 167 closedir(d); 168 return; 169 } 170 } 171 closedir(d); 172 } 173 174 end = check_whitelist(mod, module_whitelist); 175 176 /* Check that end is not NULL (whitelist succeeded) and that it is the end of the string */ 177 if(!end || *end) error(argc, argv); 178 179 180 } 181 182 int main(int argc, char **argv) { 183 char *mod; 184 int i; 185 int inserting = 0; 186 int res; 187 struct utsname u; 188 char buf[4096]; 189 char **exec_argv; 190 191 if(geteuid() != 0) { 192 fprintf(stderr, "module_helper is not setuid root\n"); 193 return 1; 194 } 195 /* drop root privs temporarily */ 196 res = seteuid(getuid()); 197 if(res != 0) 198 { 199 perror("seteuid"); 200 return 1; 201 } 202 203 res = uname(&u); 204 if(res != 0) 205 { 206 perror("uname"); 207 return 1; 208 } 209 210 res = snprintf(buf, sizeof(buf), "/usr/realtime-%s/modules", u.release); 211 if(res < 0 || res >= sizeof(buf)) 212 { 213 perror("snprintf"); 214 return 1; 215 } 216 path_whitelist[2] = buf; 217 218 if(argc < 3) error(argc, argv); 219 if(strcmp(argv[1], "insert") && strcmp(argv[1], "remove")) error(argc, argv); 220 exec_argv = malloc(argc * sizeof(char *)); 221 222 if(!strcmp(argv[1], "insert")) 223 inserting = 1; 224 225 mod = argv[2]; 226 227 if(inserting) { 228 check_whitelist_module_path(mod, argc, argv); 229 230 exec_argv[0] = "/sbin/insmod"; 231 exec_argv[1] = mod; 232 233 for(i=3; i<argc; i++) { 234 exec_argv[i-1] = argv[i]; 235 } 236 exec_argv[argc-1] = NULL; 237 } else { 238 check_whitelist_module(mod, argc, argv); 239 exec_argv[0] = "/sbin/rmmod"; 240 exec_argv[1] = mod; 241 exec_argv[2] = NULL; 242 } 243 244 /* reinstate root privs */ 245 res = seteuid(0); 246 if(res != 0) 247 { 248 perror("seteuid"); 249 return 1; 250 } 251 252 execve(exec_argv[0], exec_argv, NULL); 253 254 perror("execv failed"); 255 return 1; 256 } 257 258 /* vim:sts=4:et:sw=4 259 */