Tcl Source Code

View Ticket
Login
Ticket UUID: 2f8bb4800932dd6fa42197835d039ba21209013c
Title: `clock -locale` escapes safe interp via `~` path (msgcat 1.6.2/1.7.2)
Type: Bug Created on: 2026-10-20 18:16:26
Submitter: bef Assigned to: jan.nijtmans
Subsystem: 30. msgcat Package Severity: Severe
Priority: 5 Medium Last modified: 2026-10-29 08:28:52
Status: Closed Closed by: jan.nijtmans
Resolution: Fixed Closed on: 2026-10-29 08:28:52
Version: 8.6.18, 9.0.5, 9.1.0
Description:

The locale path check added in msgcat 1.6.2 / 1.7.2 (regexp {^[/\\]|^[a-z]+:|(?:^|[/\\])\.\.}, from ticket 301613) does not reject a leading ~. [clock] is aliased into safe interpreters and runs in the parent, and msgcat::Load does file join $langdir $locale.msg then source. On 8.6, file join tilde-expands, so a safe child can make the trusted parent source any .msg file reachable through ~/~user — a full sandbox escape (arbitrary code in the parent). mclocale and clock's -timezone guard both reject such values; the -locale → mcpackagelocale path is weaker.

PoC (Tcl 8.6 with msgcat 1.6.2):

file mkdir ~/tcldemo
set fd [open ~/tcldemo/x.msg w]
puts $fd {puts "PWNED issafe=[interp issafe] exec=[info commands exec]"}
close $fd

interp eval [interp create -safe] {clock format 0 -gmt 1 -locale ~/tcldemo/x}

file delete ~/tcldemo/x.msg

Output:

PWNED issafe=0 exec=exec

i.e. x.msg ran in the trusted parent (issafe=0, exec present), from a one-line call inside a safe child. 9.x is not escapable this way (there file join leaves a leading ~ literal), but it is still affected by the related bypass below.

Fix: also reject a leading ~ (or reuse mclocale's $v eq [file tail $v] test). A related gap on 9.x (msgcat 1.7.2): mcpackagelocale preferences <locale> sets the preference list straight from its args and calls Load without going through getpreferences, so the new regexp never runs and even an absolute path still loads — e.g. mcpackagelocale preferences /abs/x sources /abs/x.msg where mcpackagelocale set /abs/x is correctly rejected. (On 8.6 preferences is read-only, so that route is 9.x-only.) Validating centrally in msgcat::Load would cover every route at once.

User Comments:
jan.nijtmans added on 2026-10-20 21:17:15:

@sebres, this one is for you, since it's related to [a7fb3fdd6381c4f3|this] commit


serhiy.storchaka added on 2026-10-23 07:24:03:

Confirmed. On main and 9.0, mcpackagelocale preferences loads its arguments without the locale check, so an absolute path is sourced; on 8.6 the leading ~ route works as described.

Proposed fix in branch msgcat-unsafe-locale (for main and 9.0): one check for all routes, which also rejects a leading ~, applied in getpreferences, to every argument of mcpackagelocale preferences, and in msgcat::Load before a catalog file is sourced, as suggested. Tests msgcat-12.13 to 12.15. 8.6 needs a separate change, since preferences is read-only there. The msgcat version is not bumped.


serhiy.storchaka added on 2026-10-23 07:38:26:

Proposed fix for 8.6 in branch msgcat-unsafe-locale-8-6: the same check, which rejects a leading ~, in mclocale, mcpackagelocale set and msgcat::Load. Test msgcat-1.15 runs the example from this ticket in a safe interp, with HOME set to the temporary directory.


jan.nijtmans added on 2026-10-29 08:25:43:

Fixed in [85a543ddd208f448|trunk], core-9-0-branch and core-8-6-branch

Many thanks!


jan.nijtmans added on 2026-10-29 08:28:52:

> The msgcat version is not bumped

There's no need to bump the msgcat version, because msgcat 1.6.2/1.7.2 were never released in a public Tcl release.