bash - 環境変数から関数を取得する機能のセキュリティ強化

CVE-2014-6271やCVE-2014-7169では、任意の環境変数定義で起こりうることが問題の1つでした。そこで、bashの起動時に設定された環境変数が関数なのか変数なのかを判断するロジックについて修正が行われました。

※このパッチはCVE-2014-6277とCVE-2014-6278を修正としてリリースされました。しかし、CVE-2014-6277, CVE-2014-6278は非公開のため詳細は不明です。

従来は環境変数の値の先頭4文字「() {」のみで関数か変数かを判断していましたが、修正後は環境変数の名前もチェックし、環境変数の名前が「BASH_FUNC_」で始まり、「%%」で終わる場合のみ関数とみなすように変更されました。

この修正によりセキュリティが強化されると同時に、同じ名前の関数と変数を環境変数から取得できるようになりました。

$ env foo=variable 'BASH_FUNC_foo%%=() { echo function; }' ./bash -c 'echo $foo; foo'
variable
function

それでは、実際に行われた修正内容を見ていくことにしましょう。(bash43-027)

+ #define BASHFUNC_PREFIX		"BASH_FUNC_"
+ #define BASHFUNC_PREFLEN	10	/* == strlen(BASHFUNC_PREFIX */
+ #define BASHFUNC_SUFFIX		"%%"
+ #define BASHFUNC_SUFFLEN	2	/* == strlen(BASHFUNC_SUFFIX) */

先ほど解説した、「BASH_FUNC_」と「%%」が定義されています。

***************
*** 350,369 ****
        /* If exported function, define it now.  Don't import functions from
  	 the environment in privileged mode. */
!       if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
  	{
  	  string_length = strlen (string);
! 	  temp_string = (char *)xmalloc (3 + string_length + char_index);
  
! 	  strcpy (temp_string, name);
! 	  temp_string[char_index] = ' ';
! 	  strcpy (temp_string + char_index + 1, string);
  
  	  /* Don't import function names that are invalid identifiers from the
  	     environment, though we still allow them to be defined as shell
  	     variables. */
! 	  if (legal_identifier (name))
! 	    parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);
  
! 	  if (temp_var = find_function (name))
  	    {
  	      VSETATTR (temp_var, (att_exported|att_imported));
--- 355,385 ----
        /* If exported function, define it now.  Don't import functions from
  	 the environment in privileged mode. */
!       if (privmode == 0 && read_but_dont_execute == 0 && 
!           STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN) &&
!           STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN) &&
! 	  STREQN ("() {", string, 4))
  	{
+ 	  size_t namelen;
+ 	  char *tname;		/* desired imported function name */
+ 
+ 	  namelen = char_index - BASHFUNC_PREFLEN - BASHFUNC_SUFFLEN;
+ 
+ 	  tname = name + BASHFUNC_PREFLEN;	/* start of func name */
+ 	  tname[namelen] = '\0';		/* now tname == func name */
+ 
  	  string_length = strlen (string);
! 	  temp_string = (char *)xmalloc (namelen + string_length + 2);
  
! 	  memcpy (temp_string, tname, namelen);
! 	  temp_string[namelen] = ' ';
! 	  memcpy (temp_string + namelen + 1, string, string_length + 1);
  
  	  /* Don't import function names that are invalid identifiers from the
  	     environment, though we still allow them to be defined as shell
  	     variables. */
! 	  if (absolute_program (tname) == 0 && (posixly_correct == 0 || legal_identifier (tname)))
! 	    parse_and_execute (temp_string, tname, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);
  
! 	  if (temp_var = find_function (tname))
  	    {
  	      VSETATTR (temp_var, (att_exported|att_imported));
***************
*** 378,383 ****
  		}
  	      last_command_exit_value = 1;
! 	      report_error (_("error importing function definition for `%s'"), name);
  	    }
  	}
  #if defined (ARRAY_VARS)
--- 394,402 ----
  		}
  	      last_command_exit_value = 1;
! 	      report_error (_("error importing function definition for `%s'"), tname);
  	    }
+ 
+ 	  /* Restore original suffix */
+ 	  tname[namelen] = BASHFUNC_SUFFIX[0];
  	}
  #if defined (ARRAY_VARS)

環境変数の名前が「BASH_FUNC_」で始まり、「%%」で終わる場合のみ関数とみなすように変更されています。例えば、環境変数BASH_FUNC_foo%%と定義すると、bash内ではその関数をfooとして参照できます。環境変数fooも定義しておけば、fooを変数としても参照でき、それらは明確に区別されます。また、absolute_program (tname) == 0 が追加されているので、名前に「/」を含む環境変数も関数とはみなされません。

*** 2955,2959 ****
  
    INVALIDATE_EXPORTSTR (var);
!   var->exportstr = mk_env_string (name, value);
  
    array_needs_making = 1;
--- 2974,2978 ----
  
    INVALIDATE_EXPORTSTR (var);
!   var->exportstr = mk_env_string (name, value, 0);
  
    array_needs_making = 1;
***************
*** 3853,3871 ****
  
  static inline char *
! mk_env_string (name, value)
       const char *name, *value;
  {
!   int name_len, value_len;
!   char	*p;
  
    name_len = strlen (name);
    value_len = STRLEN (value);
!   p = (char *)xmalloc (2 + name_len + value_len);
!   strcpy (p, name);
!   p[name_len] = '=';
    if (value && *value)
!     strcpy (p + name_len + 1, value);
    else
!     p[name_len + 1] = '\0';
    return (p);
  }
--- 3872,3911 ----
  
  static inline char *
! mk_env_string (name, value, isfunc)
       const char *name, *value;
+      int isfunc;
  {
!   size_t name_len, value_len;
!   char	*p, *q;
  
    name_len = strlen (name);
    value_len = STRLEN (value);
! 
!   /* If we are exporting a shell function, construct the encoded function
!      name. */
!   if (isfunc && value)
!     {
!       p = (char *)xmalloc (BASHFUNC_PREFLEN + name_len + BASHFUNC_SUFFLEN + value_len + 2);
!       q = p;
!       memcpy (q, BASHFUNC_PREFIX, BASHFUNC_PREFLEN);
!       q += BASHFUNC_PREFLEN;
!       memcpy (q, name, name_len);
!       q += name_len;
!       memcpy (q, BASHFUNC_SUFFIX, BASHFUNC_SUFFLEN);
!       q += BASHFUNC_SUFFLEN;
!     }
!   else
!     {
!       p = (char *)xmalloc (2 + name_len + value_len);
!       memcpy (p, name, name_len);
!       q = p + name_len;
!     }
! 
!   q[0] = '=';
    if (value && *value)
!     memcpy (q + 1, value, value_len + 1);
    else
!     q[1] = '\0';
! 
    return (p);
  }
***************
*** 3953,3957 ****
  	     using the cached exportstr... */
  	  list[list_index] = USE_EXPORTSTR ? savestring (value)
! 					   : mk_env_string (var->name, value);
  
  	  if (USE_EXPORTSTR == 0)
--- 3993,3997 ----
  	     using the cached exportstr... */
  	  list[list_index] = USE_EXPORTSTR ? savestring (value)
! 					   : mk_env_string (var->name, value, function_p (var));
  
  	  if (USE_EXPORTSTR == 0)

同じ名前で関数と変数をexportすることが可能になったため、bash内ではそれらを区別できる必要があります。そこで、mk_env_string関数にexportされたのが関数か変数かを区別するための第3引数isfuncが追加されました。もちろん、呼び出し元も修正されています。