Skip to content

fexpand filename %/# expansion is shell-unsafe → command injection via !cmd % #784

@mat-mo

Description

@mat-mo

The ! shell-escape command in less (command.c:322 A_SHELL branch) processes the user-typed string with fexpand(), then hands the result to lsystem():

case A_SHELL: {
    ...
    if (*cbuf != '!')
    {
        if (shellcmd != NULL)
            free(shellcmd);
        shellcmd = fexpand(cbuf);   /* (1) expands % and # */
    }
    ...
    lsystem(shellcmd, done_msg);
    break;
}

fexpand() substitutes % with the current filename and # with the previous filename, using xcpy_filename():

static void xcpy_filename(xcpy *xp, constant char *str)
{
    /* If filename contains spaces, quote it
     * to prevent edit_list from splitting it. */
    lbool quote = (strchr(str, ' ') != NULL);
    if (quote)
        xcpy_char(xp, openquote);
    for (;  *str != '\0';  str++)
        xcpy_char(xp, *str);
    if (quote)
        xcpy_char(xp, closequote);
}

The function quotes only when the filename contains a space. Filenames containing other shell metacharacters (;, |, &, $, <, >, backticks, (, ), etc.) but no space pass through verbatim. When lsystem() then hands the assembled command to $SHELL -c, the inner shell parses these as metacharacters.

lsystem()'s call to shell_quote(cmd) protects only the outer-shell level — the inner shell sees the original cmd and parses its metachars normally.

Reproduction

$ mkdir -p /tmp/metafile_test
$ echo 'innocent' > '/tmp/metafile_test/foo;rm'
$ less '/tmp/metafile_test/foo;rm'
:!cat %

cat foo;rm is what reaches the inner shell — the ; is unquoted because the filename contains no space.

In a more realistic attack model the user opens a directory of files (e.g. a cloned repo) containing one with shell metacharacters in its name, then uses !cmd % against it. Filenames with metacharacters but no spaces are uncommon but achievable in attacker-controlled directories.

I think using shell_quote() rather than the space-only check is safer, similar to what is done with the %g prompt protochar in prompt.c:342 which already uses shell_quote() correctly for the v (visual) editor path; aligning xcpy_filename with that approach removes the asymmetry.

Of course, if the user uses single quotes around the CMD command the problem is avoided.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions