[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Subprocess execution with protection against parameter modification
- From: sur-behoffski <sur_behoffski@...>
- Date: Tue, 9 Feb 2021 10:36:12 +1030
G'day,
[This message should be threaded, but I read the list in
Digest mode, so apologies for any thread breakage.]
A query came up in the last couple of days regarding
executing commands but protecting parameters against
unwanted modification by special character substitution
by the shell.
I'm on GNU/Linux; not sure if the following suggestion
works on Windows(R).
I use the Lua Rock "luaposix", which contains a slew of
functions to work with the Posix interface of an OS.
Subprocesses can be set up by:
Facilitating stdin/stdout/stderr connections:
posix.pipe()
posix.dup2()
posix.fileno()
Having a single-word command (Cmd), plus all
arguments in a single table (CmdlineArgs); each of
these are interpreted as ASCII/UTF-8 C strings -- the
first NUL ends the string.
Using execp() in the child process to search the
environment path for the command (e.g. "cp"), but
otherwise making no changes to CmdlineArgs; and
Parent: Close the child end of the interconnecting
pipes, and return the child's process ID (pid), along
with the parent-end stdin/stdout/stderr file
descriptors; and
Child: Close the parent end of the interconnecting
pipes, then use posix.dup2 to redirect the child's
stdin/stdout/stderr to each go to its respective
parent pipe; and
Child: Use posix.execp to execute the command,
together with its command-line arguments.
The code for this is part of a module, and is reproduced
below:
--- Obtain pipes for stdin/stdout/stderr, fork the child process from
-- the parent, and:
--
-- (a) **Child**: Set up its end of the pipes, and execute the specified
-- command with the given arguments; or
--
-- (b) **Parent**: Close down the child end of the pipes, and hand details
-- of the child's operation, e.g. pid, fd0, fd1 and fd2, to the caller.
function M.raw_exec(Command, CmdlineArgs)
assert(Command, "missing command name")
CmdlineArgs = CmdlineArgs or {}
local Pipes = GetNonstdPipes(3)
local pid = posix.fork()
if pid < 0 then
return nil, "raw_exec: Unable to fork(): "
.. posix.errno(pid)
end
if pid ~= 0 then
-- Parent: Close child end of pipes
assert(posix.close(Pipes[1].Rd))
assert(posix.close(Pipes[2].Wr))
assert(posix.close(Pipes[3].Wr))
-- Report PID and piped FDs to caller
return pid, Pipes[1].Wr, Pipes[2].Rd, Pipes[3].Rd
end
-- Child: Close parent end of pipes
assert(posix.close(Pipes[1].Wr))
assert(posix.close(Pipes[2].Rd))
assert(posix.close(Pipes[3].Rd))
-- Change child's stdout/stdin/stderr to use its end of the
-- pipes.
assert(posix.dup2(Pipes[1].Rd, posix.fileno(io.stdin)))
assert(posix.dup2(Pipes[2].Wr, posix.fileno(io.stdout)))
assert(posix.dup2(Pipes[3].Wr, posix.fileno(io.stderr)))
-- Finally, execute the requested command in the child
-- environment.
local _, Errstr
_, Errstr = posix.execp(Command, CmdlineArgs)
-- Execp should overwrite the Lua interpreter and replace it
-- with the command, so, if Lua is still functioning here,
-- something went terribly wrong. Try to retrieve an error
-- string (presumably from the command shell), and write it to
-- the parent's stderr pipe.
PosixUnistd.write(Pipes[3].Wr, Errstr)
io.flush()
os.exit(1)
end
After that, the parent uses posix.poll to handle events
from the child, including gathering output, writing input
and handling the child-process-exit (HUP signal) case. This
is called with a timeout, so it does not consume large
portions of CPU time polling file descriptors.
At the end, the results, especially including any child
output text, stderr text, and the exit status of the
child's program, are reported to the caller.
------------------------------------
The above is lifted almost literally from a demonstration
program in the luaposix package. I've gathered the bits
into a module I've called "PosixExec", which makes life
easier for the user:
local posix = require("posix") -- optional
local PE = require("PosixExec" -- PosixExec.lua a "pseudo-rock"
local Result
local CmdlineArgs
-- Dest comes from elsewhere
CmdlineArgs {"-p", "--", Source"}
if Dest == "" then
Dest = "unknown-destination"
end
CmdlineArgs[#CmdlineArgs + 1] = Dest
Result = PE.exec("cp", CmdlineArgs)
PE.ValidateRun(Result)
print("Child stdout: ", Result.stdout)
print("Child stderr: ", Result.stderr)
There is an ancient version of PosixExec.lua floating around
the Internet already; if this list is strongly interested,
I'll post the current (MIT Licensed) version to this list;
which 375 lines, 15077 bytes.
(The latest uses std.normalize and std.strict, to provide
some standardization in the face of different Lua versions.)
----
cheers,
sur-behoffski (Brenton Hoff)
programmer, rouse Software