/ src / module_helper / module_helper.c
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   */