Quantcast
Channel: User Kamil Maciorowski - Super User
Viewing all articles
Browse latest Browse all 649

Answer by Kamil Maciorowski for Advanced argument escaping with find + xargs and nested commands

$
0
0

Analysis

The problem arises because you pass the pathname (from the expansion of {}) inside the shell code your sh -c gets. sh is not aware {} was there, it gets the argument with {} already expanded (by xargs), it interprets the whole string as shell code.

When embedding {} in shell code, there is no firm way to quote what it expands to. I mean: whatever you think of to make weird pathnames kinda work ("{}", '{}' etc.), I can always come up with a pathname that will break your specific code; not only break your code, but also inject my code. E.g. your snippet that "works":

find remove_afterwards/ -type f -print0 | xargs -0 -I{} sh -c 'echo "{}"'

can be exploited by creating a file literally named $(beep) (the command to create such file will be touch 'remove_afterwards/$(beep)'). Most likely beep is harmless, I could have used reboot or rm something though.


Solution

The safe way is to pass the expanded {} as a separate argument that will become a positional parameter for your sh -c:

find remove_afterwards/ -type f -print0 \| xargs -0 -I{} sh -c 'echo "$1" "$(stat -c "%s" "$1")"' find-sh {}

(find-sh is explained here: What is the second sh in sh -c 'some shell code' sh?)


Explanation

In the solution the code our sh -c interprets does not contain what {} has just expanded to. The code is totally static, i.e. known in advance, it is exactly what's inside the single-quotes. The expanded {} is passed to sh as a positional parameter, sh knows it is not code. Wherever you need the value inside the shell code, use $1. It's important to make $ single-quoted or escaped for the outer shell, but later $1 should be properly quoted for the inner shell (note the inner shell will see quotes and backslashes that remain after the outer shell removes quotes and backslashes it considers special). The point is sh will expand $1 on its own, still knowing that the expanded value is not shell code.


Broader picture

The same problem (with the same solution) exists in case of find -exec. Compare Is it possible to use find -exec sh -c safely?

In general you should always prefer static shell code (if possible*), unless the variable part is sanitized or deliberately designed to be interpreted as shell code. This applies whenever you are tempted to embed in the shell code anything expanded "outside of" the shell that is going to interpret the code. It does not have to be {}; e.g. it may be an outside variable you want to use from within sh -c:

# flawed: embedding in shell codevar="I want to print '"'$(beep)'"'"sh -c "echo '$var'"
# right: passing as positional parametervar="I want to print '"'$(beep)'"'"sh -c 'echo "$1"' sh "$var"
# also right: passing in environmentvar="I want to print '"'$(beep)'"'"var="$var" sh -c 'echo "$var"'

* Building static shell code is not always possible. E.g. a command run via ssh is always a single string to be interpreted by the remote shell as shell code; so if you need to pass the value of a local variable then there is no other way than embedding it in the shell code. Still there are ways to do this right (for Bash: see ${parameter@operator} when the operator is Q).


Hint

If you choose building static shell code for sh -c, I advise to single-quote the whole code. The only "problematic" character will be the single-quote itself (in case you need it to belong to the shell code argument). You may find this useful: How can I single-quote or escape the whole command line in Bash conveniently?


Viewing all articles
Browse latest Browse all 649

Trending Articles