[v6] intl: Restrict path traversal when using LANGUAGE env var [BZ #17142, CVE-2026-84243]

Message ID 20260909051953.212230-1-avinal.xlvii@gmail.com (mailing list archive)
State New
Headers
Series [v6] intl: Restrict path traversal when using LANGUAGE env var [BZ #17142, CVE-2026-84243] |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
redhat-pt-bot/TryBot-32bit success Build for i686
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 fail Test failed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Avinal Kumar Sept. 9, 2026, 5:19 a.m. UTC
  The fix for CVE-2014-0475 (Bug 17137) added valid_locale_name() in
locale/findlocale.c to reject locale names containing ".." path
components.  However, the LANGUAGE environment variable processing in
intl/dcigettext.c was not covered by that fix.  An attacker who can
set LANGUAGE (e.g. via SSH AcceptEnv) can force any gettext-using
program to load a crafted .mo file from an arbitrary filesystem
location via directory traversal.

Remove the ENABLE_SECURE gate from the IS_PATH_WITH_DIR check so it
applies to all binaries, not just SUID/SGID ones, and extend it to
also reject the bare ".." entry which contains no directory separator
but still names the parent directory.

Add tst-gettext-path-traversal to verify that locale names containing
path separators or equal to ".." are rejected by the LANGUAGE processing loop.

This fixes bug 17142 and CVE-2026-84243.

Suggested-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
---
Changes from v5:
- Removed the bare '.' rejection, LANGUAGE=. entry simply
  resolves to the bound directory itself and poses no traversal
  risk, so filtering it is unnecessary.
- Reworded NEWS entry.

Even I was not sure about the ".", thank you for clarifying.

 NEWS                              |  4 +-
 intl/Makefile                     |  4 ++
 intl/dcigettext.c                 |  8 +--
 intl/tst-gettext-path-traversal.c | 83 +++++++++++++++++++++++++++++++
 4 files changed, 94 insertions(+), 5 deletions(-)
 create mode 100644 intl/tst-gettext-path-traversal.c
  

Comments

Bruno Haible Sept. 10, 2026, 3:45 p.m. UTC | #1
Avinal Kumar wrote:
> The fix for CVE-2014-0475 (Bug 17137) added valid_locale_name() in
> locale/findlocale.c to reject locale names containing ".." path
> components.  However, the LANGUAGE environment variable processing in
> intl/dcigettext.c was not covered by that fix.  An attacker who can
> set LANGUAGE (e.g. via SSH AcceptEnv) can force any gettext-using
> program to load a crafted .mo file from an arbitrary filesystem
> location via directory traversal.

This is intended, and it is a feature. Namely, it allows unprivileged users
to use their own message translation catalogs, for programs installed by the
distribution.

Two use-cases for this exist:

  a) Users who are not satisfied with the existing translation and want
     to add their own / modify an existing one, for their own use.
     This is the *freedom to localize*. GNU as an operating system that
     takes the user's freedom seriously should not take away this freedom.

  b) Translators who want to try out their translation before submitting
     them upstream. For instance, the meaning of some messages depends
     upon which dialog they are located in — which is not visible from
     the PO files.

The workaround for a user would be to use 'sudo' to add/overwrite a message
catalog under /usr/share/locale/, if they have the permissions. But AFAIU,
even this is not possible in "immutable" distros.

POSIX [1] has specified another mechanism for this feature: The NLSPATH
environment variable. This other mechanism is currently not implemented
in GNU gettext's libintl, nor in glibc/intl/. Therefore, if your patch
was to be applied, users would lose a useful feature.

I therefore object to applying this patch *now*.

Instead, what I would propose is:
  1. to implement the NLSPATH search in GNU gettext's libintl and in
     glibc/intl/ (likely first in GNU gettext, since bugs in the first
     implementation would have less impact than in glibc).
  2. Then only, apply your patch that restricts the value of LANGUAGE.

Bruno

[1] https://pubs.opengroup.org/onlinepubs/9799919799/functions/gettext.html
  
Adhemerval Zanella Netto Sept. 14, 2026, 12:41 p.m. UTC | #2
On 10/09/26 12:45, Bruno Haible wrote:
> Avinal Kumar wrote:
>> The fix for CVE-2014-0475 (Bug 17137) added valid_locale_name() in
>> locale/findlocale.c to reject locale names containing ".." path
>> components.  However, the LANGUAGE environment variable processing in
>> intl/dcigettext.c was not covered by that fix.  An attacker who can
>> set LANGUAGE (e.g. via SSH AcceptEnv) can force any gettext-using
>> program to load a crafted .mo file from an arbitrary filesystem
>> location via directory traversal.
> 
> This is intended, and it is a feature. Namely, it allows unprivileged users
> to use their own message translation catalogs, for programs installed by the
> distribution.
> 
> Two use-cases for this exist:
> 
>   a) Users who are not satisfied with the existing translation and want
>      to add their own / modify an existing one, for their own use.
>      This is the *freedom to localize*. GNU as an operating system that
>      takes the user's freedom seriously should not take away this freedom.
> 
>   b) Translators who want to try out their translation before submitting
>      them upstream. For instance, the meaning of some messages depends
>      upon which dialog they are located in — which is not visible from
>      the PO files.
> 
> The workaround for a user would be to use 'sudo' to add/overwrite a message
> catalog under /usr/share/locale/, if they have the permissions. But AFAIU,
> even this is not possible in "immutable" distros.
> 
> POSIX [1] has specified another mechanism for this feature: The NLSPATH
> environment variable. This other mechanism is currently not implemented
> in GNU gettext's libintl, nor in glibc/intl/. Therefore, if your patch
> was to be applied, users would lose a useful feature.

Right, but this requirement adds a quite large and complex new feature to
a *security* issue. Avinal can give you more information, since he did the
research for this bug, but this is very similar to CVE-2015-0475.

Also, this make backporting way more complex.  Even though glibc have supported
<libintl.h> for long time, it would mean backport POSIX 2024 features.

> 
> I therefore object to applying this patch *now*.
> 
> Instead, what I would propose is:
>   1. to implement the NLSPATH search in GNU gettext's libintl and in
>      glibc/intl/ (likely first in GNU gettext, since bugs in the first
>      implementation would have less impact than in glibc).
>   2. Then only, apply your patch that restricts the value of LANGUAGE.
> 
> Bruno
> 
> [1] https://pubs.opengroup.org/onlinepubs/9799919799/functions/gettext.html
  
Adhemerval Zanella Netto Sept. 14, 2026, 12:44 p.m. UTC | #3
On 14/09/26 09:41, Adhemerval Zanella Netto wrote:
> 
> 
> On 10/09/26 12:45, Bruno Haible wrote:
>> Avinal Kumar wrote:
>>> The fix for CVE-2014-0475 (Bug 17137) added valid_locale_name() in
>>> locale/findlocale.c to reject locale names containing ".." path
>>> components.  However, the LANGUAGE environment variable processing in
>>> intl/dcigettext.c was not covered by that fix.  An attacker who can
>>> set LANGUAGE (e.g. via SSH AcceptEnv) can force any gettext-using
>>> program to load a crafted .mo file from an arbitrary filesystem
>>> location via directory traversal.
>>
>> This is intended, and it is a feature. Namely, it allows unprivileged users
>> to use their own message translation catalogs, for programs installed by the
>> distribution.
>>
>> Two use-cases for this exist:
>>
>>   a) Users who are not satisfied with the existing translation and want
>>      to add their own / modify an existing one, for their own use.
>>      This is the *freedom to localize*. GNU as an operating system that
>>      takes the user's freedom seriously should not take away this freedom.
>>
>>   b) Translators who want to try out their translation before submitting
>>      them upstream. For instance, the meaning of some messages depends
>>      upon which dialog they are located in — which is not visible from
>>      the PO files.
>>
>> The workaround for a user would be to use 'sudo' to add/overwrite a message
>> catalog under /usr/share/locale/, if they have the permissions. But AFAIU,
>> even this is not possible in "immutable" distros.
>>
>> POSIX [1] has specified another mechanism for this feature: The NLSPATH
>> environment variable. This other mechanism is currently not implemented
>> in GNU gettext's libintl, nor in glibc/intl/. Therefore, if your patch
>> was to be applied, users would lose a useful feature.
> 
> Right, but this requirement adds a quite large and complex new feature to
> a *security* issue. Avinal can give you more information, since he did the
> research for this bug, but this is very similar to CVE-2015-0475.
> 
> Also, this make backporting way more complex.  Even though glibc have supported
> <libintl.h> for long time, it would mean backport POSIX 2024 features.
I think another possibility would to harden .mo file handling, since we decided
not handle corrupt/invalid files as as a security issue (translation files are
considered to always come from a trusted source).

But I also see this as an orthogonal feature.
  
Bruno Haible Sept. 14, 2026, 2:24 p.m. UTC | #4
Adhemerval Zanella Netto wrote:
> Right, but this requirement adds a quite large and complex new feature to
> a *security* issue. Avinal can give you more information, since he did the
> research for this bug

Avinal, can you please share why you think this is a *security* issue, and
what is the attack scenario?

  * I claim that any attacker who can set environment variables on a user can
    do any malicious actions on that user:
      - Set PATH, and some programs get executed that the user did not intend.
      - Set LD_LIBRARY_PATH, and some libraries get executed that the user
        did not intend.
      - Set NLSPATH or LANGUAGE, and non-SUID programs will get .cat or .mo
        files that the user did not intend.
    But you certainly don't want to disable PATH or LD_LIBRARY_PATH lookups
    in glibc, nor the NLSPATH lookup that glibc already does for catgets.

  * In your mail from 2006-09-01
    https://inbox.sourceware.org/libc-alpha/20260901133902.184341-1-avinal.xlvii@gmail.com/
    the described attack is "set LANGUAGE (e.g. via SSH AcceptEnv)".
    But
      - The documentation of SSH AcceptEnv is clear that is it dangerous:
        https://man7.org/linux/man-pages/man5/sshd_config.5.html
          "Be warned that some environment variables could be used to
           bypass restricted user environments.  For this reason,
           care should be taken in the use of this directive."
      - On GitHub one finds some people who add LANGUAGE to AcceptEnv :
        https://github.com/search?q=%2FAcceptEnv+.*LANGUAGE%2F&type=code
        But on GitHub you also find some people who add LD_LIBRARY_PATH
        to AcceptEnv:
        https://github.com/search?q=%2FAcceptEnv+.*LD_%2F&type=code
        By the same reasoning, you would need to forbid use of
        LD_LIBRARY_PATH in glibc.

  * CVE-2014-0475 = https://sourceware.org/bugzilla/show_bug.cgi?id=17137
    was fixed in 2015. Then in
    https://sourceware.org/bugzilla/show_bug.cgi?id=17142
    Florian Weimer wrote "the vectors known at the time did not apply to
    gettext".

  * In your mail from 2006-04-16 you did not give an attack scenario, just
    "it passes through and can make the catalog lookup traverse outside the
     intended locale directory."

  * Just because something is assigned a CVE, does not mean it is a security
    issue. Anyone can register CVEs.

> Also, this make backporting way more complex.

Well, before deciding about backporting, I guess we should first agree on
whether it a security issue at all?

Bruno
  
Bruno Haible Sept. 14, 2026, 3:02 p.m. UTC | #5
Adhemerval Zanella Netto wrote:
> I think another possibility would to harden .mo file handling, since we decided
> not handle corrupt/invalid files as as a security issue (translation files are
> considered to always come from a trusted source).

Let me clarify on this topic, since it is apparently not well documented.

  Q: Can malicious .mo files crash a program?
  A: Yes. This can happen in two ways:

       - If an offset in the .mo file is out-of-range, _nl_find_msg in
         glibc/intl/dcigettext.c will do an out-of-range memory access.

       - Consider an msgid that is a format string that has N argument-
         consuming format directives. Usually the *printf invocation
         in the program passes exactly N arguments. If the translation
         is a format string that consumes more than N arguments, the
         *printf invocation encounters undefined behaviour and is likely
         to crash.
         (Note that this is a property of *printf, as available in C/C++/ObjC.
         In many other programming languages, the string formatting
         facility fails in a controlled way if passed too few arguments.)

  Q: Can glibc harden the .mo file handling, so that malicious .mo files
     can no longer crash a program?
  A: No. While glibc can protect against dereferencing out-of-range offsets,
     it can not do anything regarding the *printf invocation (because
     gettext() does not receive the information about the expected number
     of argument-consuming format directives).

  Q: Can .mo files shipped in a distribution crash the programs, and if not,
     why not?
  A: No, they cannot, because the .mo files are
       - created through "msgmerge" followed by "msgfmt -c", which avoids
         both of the dangers mentioned above,
       - installed in a read-only location (under /usr/share/locale/).

  Q: Are .mo files generally trusted to not crash the program?
  A: Yes, if they were created through "msgmerge" followed by "msgfmt -c".
     'msgfmt -c' rejects translations that are marked as c-format but don't
     have matching format string directives. 'msgmerge' makes sure that
     a malicious translator cannot circumvent this by removing the 'c-format'
     flag on a translation.

  Q: Are PO files from the translators trusted to not crash the program?
  A: No. Translators may translate format strings incorrectly, or may
     remove 'c-format' flags. It is the job of "msgmerge" and "msgfmt -c"
     to eliminate these possibilities.

  Q: Can PO files from the translators be malicious in other ways?
  A: Yes. There have been cases of translators submitting PO files full
     of swear words. Protections against such scenarios are not in place
     so far.

Bruno
  
Adhemerval Zanella Netto Sept. 14, 2026, 3:15 p.m. UTC | #6
On 14/09/26 12:02, Bruno Haible wrote:
> Adhemerval Zanella Netto wrote:
>> I think another possibility would to harden .mo file handling, since we decided
>> not handle corrupt/invalid files as as a security issue (translation files are
>> considered to always come from a trusted source).
> 
> Let me clarify on this topic, since it is apparently not well documented.
> 
>   Q: Can malicious .mo files crash a program?
>   A: Yes. This can happen in two ways:
> 
>        - If an offset in the .mo file is out-of-range, _nl_find_msg in
>          glibc/intl/dcigettext.c will do an out-of-range memory access.

That was exactly what was brought in glibc-cna mail list, and we decided to
*not* handle corrupt .mo as security issue with the additional point:

  And I think it should be considered a missed robust check, since msgfmt 
  can not generate corrupt .mo files with the expected tooling.

The question is whether if it really worth to add extra hardening for this
case. From the test of this message, I would guess not.

> 
>        - Consider an msgid that is a format string that has N argument-
>          consuming format directives. Usually the *printf invocation
>          in the program passes exactly N arguments. If the translation
>          is a format string that consumes more than N arguments, the
>          *printf invocation encounters undefined behaviour and is likely
>          to crash.
>          (Note that this is a property of *printf, as available in C/C++/ObjC.
>          In many other programming languages, the string formatting
>          facility fails in a controlled way if passed too few arguments.)
> 
>   Q: Can glibc harden the .mo file handling, so that malicious .mo files
>      can no longer crash a program?
>   A: No. While glibc can protect against dereferencing out-of-range offsets,
>      it can not do anything regarding the *printf invocation (because
>      gettext() does not receive the information about the expected number
>      of argument-consuming format directives).
> 
>   Q: Can .mo files shipped in a distribution crash the programs, and if not,
>      why not?
>   A: No, they cannot, because the .mo files are
>        - created through "msgmerge" followed by "msgfmt -c", which avoids
>          both of the dangers mentioned above,
>        - installed in a read-only location (under /usr/share/locale/).
> 
>   Q: Are .mo files generally trusted to not crash the program?
>   A: Yes, if they were created through "msgmerge" followed by "msgfmt -c".
>      'msgfmt -c' rejects translations that are marked as c-format but don't
>      have matching format string directives. 'msgmerge' makes sure that
>      a malicious translator cannot circumvent this by removing the 'c-format'
>      flag on a translation.
> 
>   Q: Are PO files from the translators trusted to not crash the program?
>   A: No. Translators may translate format strings incorrectly, or may
>      remove 'c-format' flags. It is the job of "msgmerge" and "msgfmt -c"
>      to eliminate these possibilities.
> 
>   Q: Can PO files from the translators be malicious in other ways?
>   A: Yes. There have been cases of translators submitting PO files full
>      of swear words. Protections against such scenarios are not in place
>      so far.
> 
> Bruno
  
Avinal Kumar Sept. 28, 2026, 11:09 a.m. UTC | #7
On Mon, Sep 14, 2026 at 7:54 PM Bruno Haible <bruno@clisp.org> wrote:
>
> Adhemerval Zanella Netto wrote:
> > Right, but this requirement adds a quite large and complex new feature to
> > a *security* issue. Avinal can give you more information, since he did the
> > research for this bug
>
Hello all, I was away for few weeks. Thank you Bruno for the detailed review.

> Avinal, can you please share why you think this is a *security* issue, and
> what is the attack scenario?
>
I will try to summarize the long discussions that led to the CVE assignment.

On local and ssh where an attack has full access, this would be
irrelevant. However on ssh sessions where ForceCommand is enforced
and/or LANGUAGE is added as AcceptEnv, this becomes relevant.

AcceptEnv is irrelevant to this attack. The attacker does not need the
server to accept LANGUAGE through SSH's environment mechanism. On a
default sshd with no AcceptEnv and no ForceCommand:

     $ ssh test@server LANGUAGE=../../../home/test/evil /bin/echo --help
       Aufruf: /bin/echo [KURZOPTION]... [ZEICHENKETTE]...
         oder: /bin/echo LANGOPTION
       Die HACKED auf die Standardausgabe

The remote shell interprets LANGUAGE=... as a variable assignment for
the command.  The only prerequisite is a .mo file placed somewhere
writable and basic SSH access. The mechanism by which a crafted file
is placed is beyond the scope of this weakness.

An crafted file can also be used to leak stack/heap addresses,
although in this particular case it cannot be used to bypass ASLR or
execute any real attack, but it is worth mentioning:
Example:

ssh -o SendEnv=LANGUAGE test@pisdr.neon-universe.ts.net
LANGUAGE=../../../home/test/evil /bin/ls /nonexistent
test@pisdr.neon-universe.ts.net's password:
ls: '/nonexistent' 0x74 0x1 (nil) (nil) 0x7fdbc5b320 0x556dead2b0
0x5598307b28 0x556dee1000 0x1 0x5598307b10: No such file or directory

>   * I claim that any attacker who can set environment variables on a user can
>     do any malicious actions on that user:
>       - Set PATH, and some programs get executed that the user did not intend.
>       - Set LD_LIBRARY_PATH, and some libraries get executed that the user
>         did not intend.
>       - Set NLSPATH or LANGUAGE, and non-SUID programs will get .cat or .mo
>         files that the user did not intend.
>     But you certainly don't want to disable PATH or LD_LIBRARY_PATH lookups
>     in glibc, nor the NLSPATH lookup that glibc already does for catgets.
>

glibc already considers this dangerous.  The code being patched contains:

       if (ENABLE_SECURE && IS_PATH_WITH_DIR (single_locale))
           continue;

So traversal in LANGUAGE is already blocked for SUID/SGID binaries.
The patch removes the ENABLE_SECURE gate and adds bare "..", it
extends an existing restriction, it does not introduce a new one.

Similarly, both LOCPATH and NLSPATH are in UNSECURE_ENVVARS
(sysdeps/generic/unsecvars.h). glibc position is already that
environment variables controlling file-loading paths are
security-relevant.

As per docs and for intended usecases LANGUAGE is described as
colan-separated identifiers, not a path mechanism, I would consider
current behavior as a gap that happens to be useful for some tasks.

>   * In your mail from 2006-09-01
>     https://inbox.sourceware.org/libc-alpha/20260901133902.184341-1-avinal.xlvii@gmail.com/
>     the described attack is "set LANGUAGE (e.g. via SSH AcceptEnv)".
>     But
>       - The documentation of SSH AcceptEnv is clear that is it dangerous:
>         https://man7.org/linux/man-pages/man5/sshd_config.5.html
>           "Be warned that some environment variables could be used to
>            bypass restricted user environments.  For this reason,
>            care should be taken in the use of this directive."
>       - On GitHub one finds some people who add LANGUAGE to AcceptEnv :
>         https://github.com/search?q=%2FAcceptEnv+.*LANGUAGE%2F&type=code
>         But on GitHub you also find some people who add LD_LIBRARY_PATH
>         to AcceptEnv:
>         https://github.com/search?q=%2FAcceptEnv+.*LD_%2F&type=code
>         By the same reasoning, you would need to forbid use of
>         LD_LIBRARY_PATH in glibc.

As I established earlier, AcceptEnv is irrelevant to this weakness.
Also this patch wouldn't restrict normal LANGUAGE usage.Uses can still
place their own catalog in the required directory and set LANGUAGE var
to point to it.

For the immutable distros, I believe the LANGUAGE traversal is a
fragile mechanism for custom catalogs, it requires knowing directory
depth, and most people who develop using immutable distros use toolbx
or something similar anyway for getting a mutable env.
>
>   * CVE-2014-0475 = https://sourceware.org/bugzilla/show_bug.cgi?id=17137
>     was fixed in 2015. Then in
>     https://sourceware.org/bugzilla/show_bug.cgi?id=17142
>     Florian Weimer wrote "the vectors known at the time did not apply to
>     gettext".
>

At the time the bug was opened, the known vectors indeed did not
apply. Since then, vectors have been demonstrated.

I agree (and Adhemerval stated too) that NLSPATH would be the proper
solution, and I am happy to work on it as a followup. For now I would
not like it to block a security fix.

>   * In your mail from 2006-04-16 you did not give an attack scenario, just
>     "it passes through and can make the catalog lookup traverse outside the
>      intended locale directory."
>
>   * Just because something is assigned a CVE, does not mean it is a security
>     issue. Anyone can register CVEs.
>
> > Also, this make backporting way more complex.
>
> Well, before deciding about backporting, I guess we should first agree on
> whether it a security issue at all?
>
> Bruno
>

The CVE was only assigned after a long discussion with the glibc
security team, when proper mechanisms and vectors were produced. I
accept that the details in my patch might be lacking some information.
I will update it to include more info.

Thank you
- Avinal
  

Patch

diff --git a/NEWS b/NEWS
index 56c6b581df..8350ef1679 100644
--- a/NEWS
+++ b/NEWS
@@ -13,7 +13,9 @@  Major new features:
 
 Deprecated and removed features, and other changes affecting compatibility:
 
-  [Add deprecations, removals and changes affecting compatibility here]
+* The gettext functions now reject LANGUAGE values containing '/' or
+  equal to '..' for all programs.  Previously this check only applied
+  to SUID/SGID binaries.
 
 Changes to build and runtime requirements:
 
diff --git a/intl/Makefile b/intl/Makefile
index a8b41a1993..2547de2744 100644
--- a/intl/Makefile
+++ b/intl/Makefile
@@ -61,6 +61,7 @@  $(objpfx)plural.o: $(objpfx)plural.c
 ifeq ($(run-built-tests),yes)
 ifeq (yes,$(build-shared))
 ifneq ($(strip $(MSGFMT)),:)
+tests += tst-gettext-path-traversal
 tests-special += \
 	$(objpfx)tst-translit.out \
 	$(objpfx)tst-gettext.out \
@@ -125,6 +126,7 @@  $(objpfx)tst-plural-eval.out: tst-plural-eval.sh $(objpfx)tst-plural-eval
 $(objpfx)tst-codeset.out: $(codeset_mo)
 $(objpfx)tst-gettext3.out: $(codeset_mo)
 $(objpfx)tst-gettext5.out: $(codeset_mo)
+$(objpfx)tst-gettext-path-traversal.out: $(codeset_mo)
 endif
 
 LOCALES := de_DE.ISO-8859-1 de_DE.UTF-8 en_US.ANSI_X3.4-1968 fr_FR.ISO-8859-1 \
@@ -139,6 +141,7 @@  $(objpfx)tst-gettext4.out: $(gen-locales)
 $(objpfx)tst-gettext5.out: $(gen-locales)
 $(objpfx)tst-gettext6.out: $(gen-locales)
 $(objpfx)tst-gettext-c-utf8.out: $(gen-locales)
+$(objpfx)tst-gettext-path-traversal.out: $(gen-locales)
 $(objpfx)tst-translit.out: $(gen-locales)
 endif
 
@@ -159,6 +162,7 @@  CFLAGS-tst-gettext4.c += -DOBJPFX=\"$(objpfx)\"
 CFLAGS-tst-gettext5.c += -DOBJPFX=\"$(objpfx)\"
 CFLAGS-tst-gettext6.c += -DOBJPFX=\"$(objpfx)\"
 CFLAGS-tst-plural-eval.c += -DOBJPFX=\"$(objpfx)\"
+CFLAGS-tst-gettext-path-traversal.c += -DOBJPFX=\"$(objpfx)\"
 
 ifeq ($(have-thread-library),yes)
 ifeq (yes,$(build-shared))
diff --git a/intl/dcigettext.c b/intl/dcigettext.c
index 43b99c2dea..e9611dae7a 100644
--- a/intl/dcigettext.c
+++ b/intl/dcigettext.c
@@ -591,10 +591,10 @@  DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2,
 	    *cp++ = *categoryvalue++;
 	  *cp = '\0';
 
-	  /* When this is a SUID binary we must not allow accessing files
-	     outside the dedicated directories.  */
-	  if (ENABLE_SECURE && IS_PATH_WITH_DIR (single_locale))
-	    /* Ignore this entry.  */
+	  /* Do not allow accessing files outside the dedicated
+	     directories.  */
+	  if (IS_PATH_WITH_DIR (single_locale)
+	      || strcmp (single_locale, "..") == 0)
 	    continue;
 	}
 
diff --git a/intl/tst-gettext-path-traversal.c b/intl/tst-gettext-path-traversal.c
new file mode 100644
index 0000000000..95ad326f64
--- /dev/null
+++ b/intl/tst-gettext-path-traversal.c
@@ -0,0 +1,83 @@ 
+/* Test that LANGUAGE values with path traversal are rejected [BZ #17142].
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library 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 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <libintl.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/support.h>
+
+static const char *const domaindir = OBJPFX "domaindir";
+static const char *const localedir = OBJPFX "domaindir/de_DE";
+static const char *const msgdir = OBJPFX "domaindir/de_DE/LC_MESSAGES";
+
+static int
+do_test (void)
+{
+  unsetenv ("OUTPUT_CHARSET");
+  /* LANGUAGE is only consulted if the locale is not "C" or "C.<codeset>".
+     The catalog is in ISO-8859-1, as is the locale, so no conversion of
+     the translation takes place.  */
+  xsetlocale (LC_ALL, "de_DE.ISO-8859-1");
+  textdomain ("codeset");
+
+  /* Verify that a legitimate LANGUAGE value produces the expected
+     translation.  This exercises the normal lookup path and confirms
+     that the test .mo catalog is in place.  */
+  bindtextdomain ("codeset", domaindir);
+  setenv ("LANGUAGE", "de_DE", 1);
+  TEST_COMPARE_STRING (gettext ("cheese"), "K\344se");
+
+  /* A bare ".." must be rejected.  Without the fix this would resolve
+     to msgdir/../LC_MESSAGES/codeset.mo, which is the real catalog,
+     so the translation would succeed.  */
+  bindtextdomain ("codeset", msgdir);
+  setenv ("LANGUAGE", "..", 1);
+  TEST_COMPARE_STRING (gettext ("cheese"), "cheese");
+
+  /* A relative path that leaves and re-enters the catalog directory
+     must be rejected.  Without the fix this would resolve to
+     localedir/../de_DE/LC_MESSAGES/codeset.mo.  */
+  bindtextdomain ("codeset", localedir);
+  setenv ("LANGUAGE", "../de_DE", 1);
+  TEST_COMPARE_STRING (gettext ("cheese"), "cheese");
+
+  /* Multiple levels of directory traversal must be rejected.  Without
+     the fix this would resolve to
+     msgdir/../../de_DE/LC_MESSAGES/codeset.mo.  */
+  bindtextdomain ("codeset", msgdir);
+  setenv ("LANGUAGE", "../../de_DE", 1);
+  TEST_COMPARE_STRING (gettext ("cheese"), "cheese");
+
+  /* Invalid entries in a colon-separated LANGUAGE list must be
+     skipped individually; valid entries that follow are still
+     used.  */
+  bindtextdomain ("codeset", domaindir);
+  setenv ("LANGUAGE", "..:../de_DE:de_DE", 1);
+  TEST_COMPARE_STRING (gettext ("cheese"), "K\344se");
+
+  /* A path containing "/.." must be rejected.  Use localedir so the
+     binding changes, which invalidates the DCIGETTEXT result cache.  */
+  bindtextdomain ("codeset", localedir);
+  setenv ("LANGUAGE", "LC_MESSAGES/..", 1);
+  TEST_COMPARE_STRING (gettext ("cheese"), "cheese");
+
+  return 0;
+}
+
+#include <support/test-driver.c>