I wrote this after learning of a security hole in $"..." expansion.
(See http://www.gnu.org/software/gettext/manual/html_node/bash.html
for details of that.)
It seems the maintainer of gettext is trying to push the use of the
portable sh syntax, for example

  cd $var || error "`eval_gettext \"Can\'t cd to \\\$var.\"`"

(example taken from the advanced bash scripting guide).  This strikes
me as a step backward, personally.  $"..." is so much more elegant and
efficient, since it calls gettext() in-process instead of invoking
the external gettext for every translation.

So, I considered a few workarounds to this.  One would be to pre-load
the entire .mo file into an associative array at start-up, which is
possible, but requires the installation of gettext command-line tools
on the target system; requires knowledge of the exact location of the
correct .mo file at run-time, which is problematic; requires lots of
memory to hold the entire catalog; and involves parsing msgunfmt output.

Another would be to use the `eval_gettext \"\'\\\$\"`" stuff.  *shudder*

Another would be to modify bash so that it doesn't create the security
hole in the first place.  I thought of two ways to do that: make a new
option that causes parameter expansions (etc.) to be done BEFORE the
locale expansion; or make a new option that causes parameter expansions
(etc.) not to be done at all.

Since I'm not all that familiar with the parser code, I looked through
it and found that the latter looked significantly easier.  I wrote the
patch, tested it, and here it is.

(I'm currently writing this e-mail through a set of ssh jumps, and
UTF-8 isn't working, so I've stripped out non-ASCII characters here.)

  griffon:~/tmp$ shopt -s safelocale
  griffon:~/tmp$ echo $"How are you?"
  Como esta, $(id)?
  griffon:~/tmp$ shopt -u safelocale
  griffon:~/tmp$ echo $"How are you?"
  Como esta, uid=1000(greg) gid=1000(greg) 
grupos=24(cdrom),25(floppy),29(audio),40(src),1000(greg),1007(freenet)?

Note that if you use this feature, certain script practices will have
to change.  For example,

  echo $"The answer is $answer"

should be replaced with

  printf $"The answer is %s\n" "$answer"

I always found it counter-intuitive that $"foo $bar" worked in the first
place, since I would have expected the $bar expansion to occur before
the locale expansion.  But people I've talked with said there were using
$"foo $bar" in practice, so this definitely affects them.

If Chet's horrified by my approach or my code, and would prefer to write
his own implementation, or rename the shopt to something other than
"safelocale", that's fine with me too.
--- bash-4.0/y.tab.c    2009-01-08 09:30:24.000000000 -0500
+++ bash-4.0-safelocale/y.tab.c 2009-02-28 13:22:11.133393093 -0500
@@ -459,6 +459,12 @@
 
 static REDIRECTEE redir;
 
+/* If non-zero, the result of a $"..." locale expansion is treated as if it
+   were single-quoted, instead of double-quoted.  This allows the script to
+   use $"..." without introducing security holes, but breaks backward
+   compatibility with bash 2.x/3.x $"..." treatment. */
+int safe_locale = 0;
+
 
 /* Enabling traces.  */
 #ifndef YYDEBUG
@@ -5378,9 +5384,17 @@
                  ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, 
&ttranslen);
                  xfree (nestret);
 
-                 nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 if (safe_locale)
+                   {
+                     nestret = sh_single_quote (ttrans);
+                     nestlen = strlen (nestret);
+                   }
+                 else
+                   {
+                     nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                     nestlen = ttranslen + 2;
+                   }
                  free (ttrans);
-                 nestlen = ttranslen + 2;
                  retind -= 2;          /* back up before the $" */
                }
 
@@ -5747,9 +5761,17 @@
              ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, 
&ttranslen);
              xfree (nestret);
 
-             nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+             if (safe_locale)
+               {
+                 nestret = sh_single_quote (ttrans);
+                 nestlen = strlen (nestret);
+               }
+             else
+               {
+                 nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 nestlen = ttranslen + 2;
+               }
              free (ttrans);
-             nestlen = ttranslen + 2;
              retind -= 2;              /* back up before the $" */
            }
 
@@ -6460,10 +6482,18 @@
                  ttrans = localeexpand (ttok, 0, ttoklen - 1, first_line, 
&ttranslen);
                  free (ttok);
 
-                 /* Add the double quotes back */
-                 ttok = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 /* Add the double quotes back (or single-quote it) */
+                 if (safe_locale)
+                   {
+                     ttok = sh_single_quote (ttrans);
+                     ttranslen = strlen (ttok);
+                   }
+                 else
+                   {
+                     ttok = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                     ttranslen += 2;
+                   }
                  free (ttrans);
-                 ttranslen += 2;
                  ttrans = ttok;
                }
 
--- bash-4.0/builtins/shopt.def 2009-01-13 08:43:16.000000000 -0500
+++ bash-4.0-safelocale/builtins/shopt.def      2009-02-28 13:22:14.037392967 
-0500
@@ -84,6 +84,7 @@
 extern int check_jobs_at_exit;
 extern int autocd;
 extern int glob_star;
+extern int safe_locale;
 
 #if defined (EXTENDED_GLOB)
 extern int extended_glob;
@@ -191,6 +192,7 @@
 #if defined (RESTRICTED_SHELL)
   { "restricted_shell", &restricted_shell, set_restricted_shell },
 #endif
+  { "safelocale", &safe_locale, (shopt_set_func_t *)NULL },
   { "shift_verbose", &print_shift_error, (shopt_set_func_t *)NULL },
   { "sourcepath", &source_uses_path, (shopt_set_func_t *)NULL },
   { "xpg_echo", &xpg_echo, (shopt_set_func_t *)NULL },
--- bash-4.0/doc/bash.1 2009-02-18 15:13:56.000000000 -0500
+++ bash-4.0-safelocale/doc/bash.1      2009-02-28 15:19:06.381392106 -0500
@@ -8689,6 +8689,14 @@
 This is not reset when the startup files are executed, allowing
 the startup files to discover whether or not a shell is restricted.
 .TP 8
+.B safelocale
+If set, the result of a \fB$"..."\fP locale expansion is treated
+as if it were single-quoted.
+If unset, the result is treated as if it were double-quoted, with the
+translated strings being subject to parameter expansion, command
+substitution and arithmetic expansion.
+This option is disabled by default.
+.TP 8
 .B shift_verbose
 If set, the
 .B shift

Reply via email to