1

I want to execute an external program in lua. Usually this can be done with

os.execute("run '"..arg0.."' 'arg1' arg2")

The problem with this approach is if I want to pass user input as string to an external program, user input could be '; evil 'h4ck teh system' ' and the script from above would execute like this:

/bin/bash -c "run ''; evil 'h4ck teh system' '' 'arg1' arg2"

Another problem occurs when I have '$var' as argument and the shell replaces this with its environment variable. In my particular case I have something like [[program 'set title "$My Title$"']] – so nested strings – and program parses "$My Title$" (with escape sequences) differently than '$My Title$' (as it is). Because I want to set the title as it, the best way is to have arguments like this: 'My Title'. But now the command have to be:

os.execute([[run "set title '$My Title$'"]])

But now – as I said – $My will be replaced with an empty string, because the environment does not know any variable named $My and because, I never wanted it to be replaced.

So I am looking for the usual approach with

execv("run", {"set title '"..arg0.."'", arg1, arg2})

1 Answer 1

2
local safe_unquoted = "^[-~_/.%w%%+,:@^]*$"
local function q(text, expand)   -- quoting under *nix shells
   -- "expand"
   --    false/nil: $var and `cmd` must NOT be expanded (use single quotes)
   --    true:      $var and `cmd` must be expanded (use double quotes)
   if text == "" then
      text = '""'
   elseif not text:match(safe_unquoted) then
      if expand then
         text = '"'..text:gsub('["\\]', '\\%0')..'"'
      else
         local new_text = {}
         for s in (text.."'"):gmatch"(.-)'" do
            new_text[#new_text + 1] = s:match(safe_unquoted) or "'"..s.."'"
         end
         text = table.concat(new_text, "\\'")
      end
   end
   return text
end

function execute_commands(...)
   local all_commands = {}
   for k, command in ipairs{...} do
      for j = 1, #command do
         if not command[j]:match"^[-~_%w/%.]+$" then
            command[j] = q(command[j], command.expand)
         end
      end
      all_commands[k] = table.concat(command, " ") -- space is arguments delimiter
   end
   all_commands = table.concat(all_commands, ";")  -- semicolon is commands delimiter
   return os.execute("/bin/bash -c "..q(all_commands))
end

Usage examples:

-- Usage example #1:
execute_commands(
   {"your/program", "arg 1", "$arg2", "arg-3", "~/arg4.txt"},
   {expand=true, "echo", "Your program finished with exit code $?"},
   {"ls", "-l"}
)
-- The following command will be executed:
-- /bin/bash -c 'your/program '\''arg 1'\'' '\''$arg2'\'' arg-3 ~/arg4.txt;echo "Your program finished with exit code $?";ls -l'

$arg2 will NOT be expanded into value because of single quotes around it, as you required.
Unfortunately, "Your program finished with exit code $?" will NOT be expanded too (unless you explicitly set expand=true).

-- Usage example #2:
execute_commands{"run", "set title '$My Title$'", "arg1", "arg2"}
-- the generated command is not trivial, but it does exactly what you need :-)
-- /bin/bash -c 'run '\''set title '\''\'\'\''$My Title$'\''\'\'' arg1 arg2'
Sign up to request clarification or add additional context in comments.

5 Comments

e.g. execute_commands({"echo", [[set my '$program$'; cd "finished";]]}) will execute /bin/bash 'echo '\\''set my '\\''\\'\\'''\\''$program$'\\''\\'\\'''\\''; cd "finished";'\\'''-c which result in this error: set sh: 1: cd: can't cd to finished sh: 1: \\: not found; it was never intended to execute cd
@sivizius - How a text appears inserted between /bin/bash and -c ? My code can't produce such output.
just copy-paste fail, the -c is indeed after /bin/bash. and this was with the old version of your code
This solution works, but I do not like it. I am used to have a real argument list. This solution adds some layers just to removed again by bash later. I still have some security concerns with this. But thank you, at least, this helps somehow.
@sivizius - Can you give an example of "security concerns" with this solution?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.