File: sge_execvlp.c

package info (click to toggle)
gridengine 8.1.9%2Bdfsg-10
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 56,880 kB
  • sloc: ansic: 432,689; java: 87,068; cpp: 31,958; sh: 29,429; jsp: 7,757; perl: 6,336; xml: 5,828; makefile: 4,701; csh: 3,928; ruby: 2,221; tcl: 1,676; lisp: 669; yacc: 519; python: 503; lex: 361; javascript: 200
file content (306 lines) | stat: -rw-r--r-- 10,272 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/* sge_execvlp.c -- execvp cum execlp and routines to sanitize environment
   Copyright (C) 2011 Dave Love, University of Liverpool <d.love@liv.ac.uk>

   This file is free software: you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation; either version 3 of
   the License, or (at your option) any later version.

   This file is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this file.  If not, a copy can be downloaded
   from http://www.gnu.org/licenses/lgpl.html.
*/

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <regex.h>
#include "uti2/sge_execvlp.h"
#include "sgeobj/sge_var.h"
#include "uti/sge_string.h"

extern char **environ;

static char **
newargs (const char *file, char *const argv[])
{
   int i, na = 0;
   char **newargv;

   while (argv[na++]) ;
   newargv = malloc ((na + 1) * sizeof (char *));
   if (!newargv) return NULL;
   newargv[0] = "/bin/sh";
   newargv[1] = (char *) file;
   for (i = 2; i < na + 1; i++)
      newargv[i] = argv[i-1];
   return newargv;
}

/****** uti/sge_execvlp() ****************************************
*
*  NAME
*     sge_execvlp -- like execve, but search the path
*
*  SYNOPSIS
*     #include "uti/execvlp.h"
*     int sge_execvlp (const char *file, char *const argv[], char *const envp[])
*
*  FUNCTION
*     A version of execve that does a path search for the executable
*     like execlp/execvp.
*
*  INPUTS
*     file - name of executable
*     argv - null-terminated list of arguments
*     envp - null-terminated list of environment elements
*
*  RESULT
*     on success, the function will not return (it execs)
*     -1 if the exec fails
******************************************************************************/
int
sge_execvlp (const char *file, char *const argv[], char *const envp[])
{
   char *path;
   char execpath[SGE_PATH_MAX];
   char *component;
   int first = 1;

   path = getenv ("PATH");
   if (!path)                    /* use file directly if no path */
      return execve (file, argv, envp);

   if (strchr (file, '/')) {	/* directory path -- don't search */
      int late_errno = errno;

      errno = 0;
      execve (file, argv, envp);
      if (errno == ENOEXEC) {
	 char **newargv;

         errno = late_errno;
         newargv = newargs (file, argv);
         if (!newargv) return -1;
	 execve (newargv[0], newargv, envp);
	 free (newargv);
      }
      return -1;
   }

   /* Else try path components */
   path = strdup (path);
   while ((component = strtok ((first ? path : NULL), ":"))) {
      int late_errno = errno;

      first = 0;
      execpath[0] = '\0';
      if (strlen (component) != 0) { /* else current directory */
         if ((sge_strlcat(execpath, component, sizeof(execpath))
              >= sizeof(execpath))
             || (sge_strlcat(execpath, "/", sizeof(execpath))
                 >= sizeof(execpath)))
            continue;
      }
      if (sge_strlcat(execpath, file, sizeof(execpath)) >= sizeof(execpath))
         continue;
      errno = 0;
      execve (execpath, argv, envp);
      if (errno == ENOEXEC) {
	 char **newargv;

         newargv = newargs (file, argv);
         if (!newargv) return -1;
	 execve (newargv[0], newargv, envp);
	 free (newargv);
         errno = late_errno;
	 sge_free(&path);
	 return -1;
      }
      if (errno != ENOENT)
	 return -1;
   }
   sge_free(&path);
   return -1;
}

/* Fixme:  Instead of semi-hardwiring (parts of) environment variables
   below, we should make them execd parameters, but that requires (a)
   infrastructure to allow list-valued execd parameters, (b) ensuring
   it gets set up.  */

/* Check whether an environment string (foo=bar) sets a variable in
   the unsafe list or with a bad value.  In retrospect, this stuff
   should probably just have been lifted from sudo.  */
static bool
var_unsafe(char *var, regex_t lc_regex)
{
   const char *unsafe[] = {
      /* These variables are regarded as unsafe in suid programs by
         GNU libc (2.11.1).
         Fixme:  Try to extend this for other systems.  */
      "GCONV_PATH", "GETCONF_DIR", "HOSTALIASES",
      /* We get everything with an LD_ prefix anyhow. */
      /*
        "LD_AUDIT", "LD_DEBUG", "LD_DEBUG_OUTPUT",
        "LD_DYNAMIC_WEAK", "LD_LIBRARY_PATH", "LD_ORIGIN_PATH",
        "LD_PRELOAD", "LD_PROFILE", "LD_SHOW_AUXV", "LD_USE_LOAD_BIAS",
      */
      "LOCALDOMAIN", "LOCPATH", "MALLOC_TRACE", "NIS_PATH",
      "NLSPATH", "RESOLV_HOST_CONF", "RES_OPTIONS", "TMPDIR", "TZDIR",
      /* Others not from glibc */
      /* LD_LIBRARY_PATH &c, and DYLD_LIBRARY_PATH taken care of by
	 regexps elsewhere.  */
      /* Fixme:  Check if IFS actually needs to be set on relevant systems
         <http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/environment-variables.html#ENV-VAR-SOLUTION>.  */
      "IFS",   /* for shell scripts, but not inherited by the ones I've tried */
      /* From sudo, but maybe not all relevant here.  (Various ones
         are elided, such as for SecurID, obsolete krb4, and those
         which can be removed by initial sanitization in shells or
         command-line options like perl -T):  */
      "SHELLOPTS", "PS4", "PATH_LOCALE", "PATH_LOCALE",
      "TERMINFO", "TERMINFO_DIRS", "TERMPATH", "TERMCAP",
      "ENV", "BASH_ENV", "KRB5_CONFIG", "KRB5_KTNAME", "JAVA_TOOL_OPTIONS",
#ifdef _AIX
      "AUTHSTATE",
#endif
      ""};
   int i;

   for (i=0; strlen (unsafe[i]) > 0; i++)
      if (strncmp (var, unsafe[i], strlen (unsafe[i])) == 0)
         return true;

   /* Check locale variable values per
      <http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/locale.html> */
   {
      const char *locale[] = {"LANGUAGE", "LANG", "LINGUAS", ""};
      for (i=0; strlen (locale[i]) > 0; i++)
         if (strncmp (var, locale[i], strlen (locale[i])) == 0
             || strncmp (var, "LC_", 3) == 0)
            if (regexec (&lc_regex, var, 0, NULL, 0) != 0) /* invalid locale */
               return true;
   }
   return false;
}

/****** uti/sanitize_environment() *****************************************
*  NAME
*     sanitize_environment() -- remove sensitive variables from the environment before calling programs
*
*  SYNOPSIS
*     #include "uti/execvlp.h"
*     void sanitize_environment(char *env[])
*
*  FUNCTION
*     Remove variables from the environment which represent a security
*     risk when a program is called under a privileged id with an
*     environment supplied by the user, such as remote
*     communication daemons.  (For instance, if the user can get a
*     dynamically-linked privileged program run in an ELF environment
*     with arbitrary LD_LIBRARY_PATH or LD_PRELOAD, all bets are off.)
*
*  SEE ALSO
*  qrsh_starter
*******************************************************************************/
void
sanitize_environment (char *env[])
{
   int i = 0, j;
   regex_t lc_regex;
   const char *sharedlib = var_get_sharedlib_path_name ();
   /* Valid locale -- see var_unsafe.  */
   const char *lc_pattern = "^[A-Za-z_]+=[A-Za-z][-A-Za-z0-9_,+@.=]*$";

   if (!env) return;
   /* We only need to bother if we're privileged in some way.  */
   if ((geteuid () != SGE_SUPERUSER_UID)
       && (getegid () != SGE_SUPERUSER_GID)
       && (geteuid () == getuid ())
       && (getegid () == getgid ()))
      return;

   if (regcomp (&lc_regex, lc_pattern, REG_NOSUB | REG_EXTENDED))
      exit(1);

   while (env[i]) {
      /* In an ELF system, sensitive variables include
         LD_LIBRARY_PATH, plus variations like LD_LIBRARY_PATH64, and
         LD_PRELOAD.  For safety, we'll zap anything with an LD_
         prefix.  (We could test the __ELF__ of the system, but this
         is safer.)  Similarly check var_get_sharedlib_path_name as a
         prefix.  */
      if ((strncmp (env[i], "LD_", 3) == 0)
          /* Known in Tru64, but sudo has it generally */
	  || (strncmp (env[i], "_RLD_", 5) == 0)
#ifdef __APPLE__
	  /* DYLD_{FALLBACK_,}FRAMEWORK_PATH,
	     {FALLBACK_,}DYLD_LIBRARY_PATH seem to be relevant.  */
	  || (strncmp (env[i], "DYLD_", 5) == 0)
#elif defined _AIX
	  || (strncmp (env[i], "LDR_", 4) == 0)
#elif defined __hpux__
          /* _HP_DLDOPTS is loader options; guess a prefix.  */
	  || (strncmp (env[i], "_HP_DLD", 7) == 0)
#endif
          || (strncmp (env[i], sharedlib, strlen (sharedlib)) == 0)
          || var_unsafe (env[i], lc_regex)) {
         /* Shuffle env elements down over the unsafe one.  */
         for (j=i; env[j]; j++)
            env[j] = env[j+1];
      } else {
         i++;
      }
   }
   regfree (&lc_regex);
   return;
}

/****** uti/sge_copy_sanitize_env() *****************************************
*  NAME
*     sge_copy_sanitize_environment() -- make a copy of environment with sensitive variables removed
*
*  SYNOPSIS
*     #include "uti/execvlp.h"
*     char **sge_copy_sanitize_env(char *env[])
*
*  FUNCTION
*     Remove variables from the environment which represent a security
*     risk if when a program is called under a privileged id with an
*     environment supplied by the user, for example, remote
*     communication daemons.  (If the user can get a
*     dynamically-linked privileged program run in an ELF environment,
*     say, with arbitrary LD_LIBRARY_PATH or LD_PRELOAD, all bets are
*     off.)  Also try to protect shell scripts from IFS, at least.
*******************************************************************************/
char **sge_copy_sanitize_env (char *const env[])
{
   int l=0;
   size_t bytes;
   char **newenv;

   while (env[l] != 0)
      l++;
   bytes = (l+1)*sizeof (char *);
   newenv = malloc (bytes);
   memcpy (newenv, env, bytes);
   sanitize_environment (newenv);
   return newenv;
}

#if TEST
int main(int argc, char* argv[])
{
   char *args[] = {""};
   sge_execvlp ("env", args, sge_copy_sanitize_env(environ));
   exit (0);
}
#endif