Thread: BUG #8702: psql \df+ translate_columns[] overflow and unexpected gettext translation

BUG #8702: psql \df+ translate_columns[] overflow and unexpected gettext translation

From
eshkinkot@gmail.com
Date:
The following bug has been logged on the website:

Bug reference:      8702
Logged by:          Sergey Burladyan
Email address:      eshkinkot@gmail.com
PostgreSQL version: 9.3.1
Operating system:   Debian testing
Description:

I get this result from \df+ foo:
--- echo $LANG
--- ru_RU.UTF-8
---
--- create function foo() returns void language plpgsql as $$ begin end $$;


Список функций
-[ RECORD 1

]----------+---------------------------------------------------------------------------------------------------------------------
Схема                  | public
Имя                    | foo
Тип данных результата  | void
Типы данных аргументов |
Тип                    | обычная
Безопасность           | вызывающего
Изменчивость           | volatile
Владелец               | seb
Язык                   | plpgsql
Исходный код           |  begin end
Описание               | Project-Id-Version: PostgreSQL 9 current
                       | Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org
                       | POT-Creation-Date: 2013-05-20 02:16+0000
                       | PO-Revision-Date: 2013-05-20 20:02+0400
                       | Last-Translator: Alexander Lakhin
<exclusion@gmail.com>
                       | Language-Team: Russian
<pgtranslation-translators@pgfoundry.org>
                       | Language: ru
                       | MIME-Version: 1.0
                       | Content-Type: text/plain; charset=UTF-8
                       | Content-Transfer-Encoding: 8bit
                       | X-Poedit-Language: Russian
                       | X-Poedit-Country: RUSSIAN FEDERATION
                       | X-Generator: Lokalize 1.5
                       | Plural-Forms: nplurals=3; plural=(n%10==1 &&
n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);
                       |


looks like "Description" field contains header from ru.mo messages file.


src/bin/psql/describe.c:describeFunctions translate_columns[] array size
is less than the maximum number of columns in query and it overflowed in
src/bin/psql/print.c:printQuery at
...
            translate = (opt->translate_columns &&
opt->translate_columns[c]);
...
so in my case opt->translate_columns[10] have garbage == true and
"Description" field
content translated unexpectedly.
On Fri, 2013-12-27 at 00:00 +0000, eshkinkot@gmail.com wrote:
> I get this result from \df+ foo:
> --- echo $LANG
> --- ru_RU.UTF-8
> ---
> --- create function foo() returns void language plpgsql as $$ begin end $$;

> src/bin/psql/describe.c:describeFunctions translate_columns[] array size
> is less than the maximum number of columns in query and it overflowed in
> src/bin/psql/print.c:printQuery at
> ...
>             translate = (opt->translate_columns &&
> opt->translate_columns[c]);
> ...
> so in my case opt->translate_columns[10] have garbage == true and
> "Description" field
> content translated unexpectedly.

There are several places that are similarly broken.  See attached patch.

Obviously, this is not a very robust programming interface, if the same
mistake has been repeated three times independently.


Attachment
Peter Eisentraut <peter_e@gmx.net> writes:
> There are several places that are similarly broken.  See attached patch.

> Obviously, this is not a very robust programming interface, if the same
> mistake has been repeated three times independently.

Yeah :-(.  How about adding an Assert as in the attached?

I had to make the Assert say ">=" not just "==" because there are numerous
callers that use the same array for both normal and verbose output.
This seems a bit shaky but it's not actually broken anywhere.
I think.

The breakage in \df appears to be the fault of the addition of a
translated "security definer/invoker" column, which clearly nobody really
tested the translations for, or they'd have noticed that the column
marking in translate_columns[] was wrong :-(.  I believe the "translation
support" in \dy (event triggers) is pretty bogus as well.

            regards, tom lane

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 96322ca..e915262 100644
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
*************** describeFunctions(const char *functypes,
*** 224,230 ****
      PQExpBufferData buf;
      PGresult   *res;
      printQueryOpt myopt = pset.popt;
!     static const bool translate_columns[] = {false, false, false, false, true, true, false, false, false, false};

      if (strlen(functypes) != strspn(functypes, "antwS+"))
      {
--- 224,230 ----
      PQExpBufferData buf;
      PGresult   *res;
      printQueryOpt myopt = pset.popt;
!     static const bool translate_columns[] = {false, false, false, false, true, true, true, false, false, false,
false};

      if (strlen(functypes) != strspn(functypes, "antwS+"))
      {
*************** describeFunctions(const char *functypes,
*** 457,462 ****
--- 457,463 ----
      myopt.title = _("List of functions");
      myopt.translate_header = true;
      myopt.translate_columns = translate_columns;
+     myopt.n_translate_columns = lengthof(translate_columns);

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

*************** permissionsList(const char *pattern)
*** 789,794 ****
--- 790,796 ----
      myopt.title = buf.data;
      myopt.translate_header = true;
      myopt.translate_columns = translate_columns;
+     myopt.n_translate_columns = lengthof(translate_columns);

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

*************** listDefaultACLs(const char *pattern)
*** 862,867 ****
--- 864,870 ----
      myopt.title = buf.data;
      myopt.translate_header = true;
      myopt.translate_columns = translate_columns;
+     myopt.n_translate_columns = lengthof(translate_columns);

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

*************** objectDescription(const char *pattern, b
*** 1034,1039 ****
--- 1037,1043 ----
      myopt.title = _("Object descriptions");
      myopt.translate_header = true;
      myopt.translate_columns = translate_columns;
+     myopt.n_translate_columns = lengthof(translate_columns);

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

*************** listTables(const char *tabtypes, const c
*** 2818,2823 ****
--- 2822,2828 ----
          myopt.title = _("List of relations");
          myopt.translate_header = true;
          myopt.translate_columns = translate_columns;
+         myopt.n_translate_columns = lengthof(translate_columns);

          printQuery(res, &myopt, pset.queryFout, pset.logfile);
      }
*************** listConversions(const char *pattern, boo
*** 2999,3005 ****
      PQExpBufferData buf;
      PGresult   *res;
      printQueryOpt myopt = pset.popt;
!     static const bool translate_columns[] = {false, false, false, false, true};

      initPQExpBuffer(&buf);

--- 3004,3011 ----
      PQExpBufferData buf;
      PGresult   *res;
      printQueryOpt myopt = pset.popt;
!     static const bool translate_columns[] =
!         {false, false, false, false, true, false};

      initPQExpBuffer(&buf);

*************** listConversions(const char *pattern, boo
*** 3055,3060 ****
--- 3061,3067 ----
      myopt.title = _("List of conversions");
      myopt.translate_header = true;
      myopt.translate_columns = translate_columns;
+     myopt.n_translate_columns = lengthof(translate_columns);

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

*************** listEventTriggers(const char *pattern, b
*** 3116,3121 ****
--- 3123,3129 ----
      myopt.title = _("List of event triggers");
      myopt.translate_header = true;
      myopt.translate_columns = translate_columns;
+     myopt.n_translate_columns = lengthof(translate_columns);

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

*************** listCasts(const char *pattern, bool verb
*** 3134,3140 ****
      PQExpBufferData buf;
      PGresult   *res;
      printQueryOpt myopt = pset.popt;
!     static const bool translate_columns[] = {false, false, false, true};

      initPQExpBuffer(&buf);

--- 3142,3148 ----
      PQExpBufferData buf;
      PGresult   *res;
      printQueryOpt myopt = pset.popt;
!     static const bool translate_columns[] = {false, false, false, true, false};

      initPQExpBuffer(&buf);

*************** listCasts(const char *pattern, bool verb
*** 3214,3219 ****
--- 3222,3228 ----
      myopt.title = _("List of casts");
      myopt.translate_header = true;
      myopt.translate_columns = translate_columns;
+     myopt.n_translate_columns = lengthof(translate_columns);

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

*************** listCollations(const char *pattern, bool
*** 3289,3294 ****
--- 3298,3304 ----
      myopt.title = _("List of collations");
      myopt.translate_header = true;
      myopt.translate_columns = translate_columns;
+     myopt.n_translate_columns = lengthof(translate_columns);

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

*************** describeOneTSParser(const char *oid, con
*** 3548,3553 ****
--- 3558,3564 ----
      myopt.topt.default_footer = false;
      myopt.translate_header = true;
      myopt.translate_columns = translate_columns;
+     myopt.n_translate_columns = lengthof(translate_columns);

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

*************** describeOneTSParser(const char *oid, con
*** 3579,3584 ****
--- 3590,3596 ----
      myopt.topt.default_footer = true;
      myopt.translate_header = true;
      myopt.translate_columns = NULL;
+     myopt.n_translate_columns = 0;

      printQuery(res, &myopt, pset.queryFout, pset.logfile);

diff --git a/src/bin/psql/print.c b/src/bin/psql/print.c
index 736225c..fdf4dcc 100644
*** a/src/bin/psql/print.c
--- b/src/bin/psql/print.c
*************** printQuery(const PGresult *result, const
*** 2596,2601 ****
--- 2596,2605 ----
      printTableInit(&cont, &opt->topt, opt->title,
                     PQnfields(result), PQntuples(result));

+     /* Assert caller supplied enough translate_columns[] entries */
+     Assert(opt->translate_columns == NULL ||
+            opt->n_translate_columns >= cont.ncolumns);
+
      for (i = 0; i < cont.ncolumns; i++)
      {
          char        align;
diff --git a/src/bin/psql/print.h b/src/bin/psql/print.h
index 9cfa3b6..41ba798 100644
*** a/src/bin/psql/print.h
--- b/src/bin/psql/print.h
*************** typedef struct printQueryOpt
*** 146,151 ****
--- 146,152 ----
      bool        translate_header;        /* do gettext on column headers */
      const bool *translate_columns;        /* translate_columns[i-1] => do
                                           * gettext on col i */
+     int            n_translate_columns;    /* length of translate_columns[] */
  } printQueryOpt;


diff --git a/src/bin/scripts/createlang.c b/src/bin/scripts/createlang.c
index 5cfba8e..92ab975 100644
*** a/src/bin/scripts/createlang.c
--- b/src/bin/scripts/createlang.c
*************** main(int argc, char *argv[])
*** 160,165 ****
--- 160,167 ----
          popt.title = _("Procedural Languages");
          popt.translate_header = true;
          popt.translate_columns = translate_columns;
+         popt.n_translate_columns = lengthof(translate_columns);
+
          printQuery(result, &popt, stdout, NULL);

          PQfinish(conn);
diff --git a/src/bin/scripts/droplang.c b/src/bin/scripts/droplang.c
index b9664a9..3650096 100644
*** a/src/bin/scripts/droplang.c
--- b/src/bin/scripts/droplang.c
*************** main(int argc, char *argv[])
*** 159,164 ****
--- 159,166 ----
          popt.title = _("Procedural Languages");
          popt.translate_header = true;
          popt.translate_columns = translate_columns;
+         popt.n_translate_columns = lengthof(translate_columns);
+
          printQuery(result, &popt, stdout, NULL);

          PQfinish(conn);

Tom Lane <tgl@sss.pgh.pa.us> writes:
> Peter Eisentraut <peter_e@gmx.net> writes:
>> Obviously, this is not a very robust programming interface, if the same
>> mistake has been repeated three times independently.

> Yeah :-(.  How about adding an Assert as in the attached?

I cleaned up the issues in \dy and committed this.

            regards, tom lane