[Tarantool-patches] [PATCH luajit 3/3] test: fix dynamic modules loading on MacOS

Sergey Ostanevich sergos at tarantool.org
Tue Apr 6 20:02:56 MSK 2021


Hi!

Thanks for the patch!

Couple of nits, LGTM.

regards,
Sergos


> On 5 Apr 2021, at 20:11, Igor Munkin <imun at tarantool.org> wrote:
> 
> This patch resolves the issue with running the tests with auxiliary
> dynamically loaded modules in case of out-of-source build.
> 
I wonder how it works for an in-source one… (just a comment)

> The first issue relates to the way modules loaded at runtime are built
					    ^ to be
> on MacOS. Since the auxiliary libraries are built as a dynamically
> loaded modules on MacOS instead of shared libraries as it is done on
> Linux and BSD, another environment variable should be used to guide
> <ffi.load> while searching the extension. Hence the values collected in

This might overlap: ffi should use ‘LUA_CPATH’, while it is the dynamic 
loader and dlopen() interface of it that relies on {DY}LD_ set of variables. 

> scope of <BuildTestCLib> macro need to be set to DYLD_LIBRARY_PATH
> variable instead of LD_LIBRARY_PATH on MacOS.
> 
> Unfortunately, this rather small change doesn't resolve the problem at
> all and the root cause lies much deeper than it seems at the beginning.
> 
> Apple tries their best to "protect their users from malicious software".
> As a result SIP[1] has been designed and released. Now, Apple developers
> are *so protected*, that they can load nothing being not installed in
> the system, since some programs sanitize the environment before they
> start child processes. Specifically, environment variables starting with
> DYLD_ and LD_ are unset for child process started by system programs[2].
> 
> That which does not kill us makes us stronger: fortunately, these
> environment variables are used by FFI machinery to find the proper
> shared library, hence we can still tweak testing environment before
> calling <ffi.load>. However, the value can't be passed via the standard
> environment variable, so we prepend TEST_ prefix to its name to get
> around SIP magic tricks. Finally, to set the variable required by FFI
> machinery the introduced <utils.tweakenv> routine is used. PROFIT!
> Your move, Cupertino geniuses.
> 
> [1]: https://support.apple.com/en-us/HT204899
> [2]: https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/RuntimeProtections/RuntimeProtections.html
> 
> Resolves tarantool/tarantool#5959
> Follows up tarantool/tarantool#4862
> 
> Co-authored-by: Sergey Ostanevich <sergos at tarantool.org>
> Co-authored-by: Mons Anderson <mons at cpan.org>
> Signed-off-by: Igor Munkin <imun at tarantool.org>
> ---
> test/tarantool-tests/CMakeLists.txt           | 39 +++++++++++++++++--
> .../gh-4427-ffi-sandwich.test.lua             |  4 ++
> .../lj-flush-on-trace.test.lua                |  4 ++
> test/tarantool-tests/utils.lua                | 39 ++++++++++++++++---
> 4 files changed, 77 insertions(+), 9 deletions(-)
> 
> diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt
> index 3e36ff86..c793ad60 100644
> --- a/test/tarantool-tests/CMakeLists.txt
> +++ b/test/tarantool-tests/CMakeLists.txt
> @@ -69,11 +69,46 @@ set(LUA_PATH
> )
> set(LUA_TEST_SUFFIX .test.lua)
> set(LUA_TEST_FLAGS --failures --shuffle)
> +set(LUA_TEST_ENV
> +  "LUA_PATH=\"${LUA_PATH}\;\;\""
> +  "LUA_CPATH=\"${LUA_CPATH}\;\;\""
> +)
> 
> if(CMAKE_VERBOSE_MAKEFILE)
>   list(APPEND LUA_TEST_FLAGS --verbose)
> endif()
> 
> +# XXX: Since the auxiliary libraries are built as a dynamically
> +# loaded modules on MacOS instead of shared libraries as it is
> +# done on Linux and BSD, another environment variable should be
> +# used to guide <ffi.load> while searching the extension.
> +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
> +  # XXX: Apple tries their best to "protect their users from
> +  # malware". As a result SIP (see the link[1] below) has been
> +  # designed and released. Now, Apple developers are so protected,
> +  # that they can load nothing being not installed in the system,
> +  # since some programs sanitize the environment before they start
> +  # child processes. Specifically, environment variables starting
> +  # with DYLD_ and LD_ are unset for child process started by
> +  # system programs (like /usr/bin/env used for preparing testing
> +  # environment). For more info, see the docs[2] below.
> +  #
> +  # That which does not kill us makes us stronger: fortunately,
> +  # these environment variables are used by FFI machinery to find
> +  # the proper shared library, hence we can still tweak testing
> +  # environment before calling <ffi.load>. However, the value
> +  # can't be passed via the standard environment variable, so we
> +  # prepend TEST_ prefix to its name to get around SIP magic
> +  # tricks. Finally, to set the variable required by FFI machinery
> +  # the introduced <utils.tweakenv> routine is used.
> +  #
> +  # [1]: https://support.apple.com/en-us/HT204899
> +  # [2]: https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/RuntimeProtections/RuntimeProtections.html
> +  list(APPEND LUA_TEST_ENV TEST_DYLD_LIBRARY_PATH="${LD_LIBRARY_PATH}")
> +else()
> +  list(APPEND LUA_TEST_ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH}")
> +endif()
> +
> # LUA_CPATH and LD_LIBRARY_PATH variables and also TESTLIBS list
> # with dependecies are set in scope of BuildTestLib macro.
> add_custom_target(tarantool-tests
> @@ -83,9 +118,7 @@ add_custom_command(TARGET tarantool-tests
>   COMMENT "Running Tarantool tests"
>   COMMAND
>   env
> -    LUA_PATH="${LUA_PATH}\;\;"
> -    LUA_CPATH="${LUA_CPATH}\;\;"
> -    LD_LIBRARY_PATH="${LD_LIBRARY_PATH}"
> +    ${LUA_TEST_ENV}
>     ${PROVE} ${CMAKE_CURRENT_SOURCE_DIR}
>       --exec '${LUAJIT_TEST_COMMAND}'
>       --ext ${LUA_TEST_SUFFIX}
> diff --git a/test/tarantool-tests/gh-4427-ffi-sandwich.test.lua b/test/tarantool-tests/gh-4427-ffi-sandwich.test.lua
> index 64df5dbd..651dc3f4 100644
> --- a/test/tarantool-tests/gh-4427-ffi-sandwich.test.lua
> +++ b/test/tarantool-tests/gh-4427-ffi-sandwich.test.lua
> @@ -3,6 +3,10 @@ local utils = require('utils')
> -- Disabled on *BSD due to #4819.
> utils.skipcond(jit.os == 'BSD', 'Disabled due to #4819')
> 
> +-- XXX: Tweak the process environment to get around SIP.
> +-- See the comment in suite CMakeLists.txt for more info.
> +utils.tweakenv(jit.os == 'OSX', 'DYLD_LIBRARY_PATH')
> +
> utils.selfrun(arg, {
>   {
>     arg = {
> diff --git a/test/tarantool-tests/lj-flush-on-trace.test.lua b/test/tarantool-tests/lj-flush-on-trace.test.lua
> index edc5cf61..1ad4f832 100644
> --- a/test/tarantool-tests/lj-flush-on-trace.test.lua
> +++ b/test/tarantool-tests/lj-flush-on-trace.test.lua
> @@ -3,6 +3,10 @@ local utils = require('utils')
> -- Disabled on *BSD due to #4819.
> utils.skipcond(jit.os == 'BSD', 'Disabled due to #4819')
> 
> +-- XXX: Tweak the process environment to get around SIP.
> +-- See the comment in suite CMakeLists.txt for more info.
> +utils.tweakenv(jit.os == 'OSX', 'DYLD_LIBRARY_PATH')
> +
> utils.selfrun(arg, {
>   {
>     arg = {
> diff --git a/test/tarantool-tests/utils.lua b/test/tarantool-tests/utils.lua
> index d2dd71b0..9bdb71ec 100644
> --- a/test/tarantool-tests/utils.lua
> +++ b/test/tarantool-tests/utils.lua
> @@ -1,7 +1,12 @@
> local M = {}
> 
> +local ffi = require('ffi')
> local tap = require('tap')
> 
> +ffi.cdef([[
> +  int setenv(const char *name, const char *value, int overwrite);
> +]])
> +

Why? os.setenv() didn’t work for some reason? Comment it and I’m fine.

tarantool> os.setenv("a","b")
---
...

tarantool> os.getenv("a")
---
- b
...

tarantool> os.execute('echo $a')
b
---

> local function luacmd(args)
>   -- arg[-1] is guaranteed to be not nil.
>   local idx = -2
> @@ -13,6 +18,12 @@ local function luacmd(args)
>   return table.concat(args, ' ', idx + 1, -1)
> end
> 
> +local function unshiftenv(variable, value, sep)
> +  local envvar = os.getenv(variable)
> +  return ('%s="%s%s"'):format(variable, value,
> +                              envvar and ('%s%s'):format(sep, envvar) or '')
> +end
> +
> function M.selfrun(arg, checks)
>   -- If TEST_SELFRUN is set, just execute the test payload below
>   -- <selfrun> call, ...
> @@ -24,18 +35,22 @@ function M.selfrun(arg, checks)
> 
>   test:plan(#checks)
> 
> +  local libext = package.cpath:match('?.(%a+);')
>   local vars = {
>     LUABIN = luacmd(arg),
>     SCRIPT = arg[0],
>     PATH   = arg[0]:gsub('%.test%.lua', ''),
> -    SUFFIX = package.cpath:match('?.(%a+);'),
> +    SUFFIX = libext,
> +    ENV = table.concat({
> +      unshiftenv('LUA_PATH', '<PATH>/?.lua', ';'),
> +      unshiftenv('LUA_CPATH', '<PATH>/?.<SUFFIX>', ';'),
> +      unshiftenv((libext == 'dylib' and 'DYLD' or 'LD') .. '_LIBRARY_PATH',
> +                 '<PATH>', ':'),
> +      'TEST_SELFRUN=1',
> +    }, ' '),
>   }
> 
> -  local cmd = string.gsub('LUA_PATH="<PATH>/?.lua;$LUA_PATH" ' ..
> -                          'LUA_CPATH="<PATH>/?.<SUFFIX>;$LUA_CPATH" ' ..
> -                          'LD_LIBRARY_PATH=<PATH>:$LD_LIBRARY_PATH ' ..
> -                          'TEST_SELFRUN=1' ..
> -                          '<LUABIN> 2>&1 <SCRIPT>', '%<(%w+)>', vars)
> +  local cmd = string.gsub('<ENV> <LUABIN> 2>&1 <SCRIPT>', '%<(%w+)>', vars)
> 
>   for _, ch in pairs(checks) do
>     local testf = test[ch.test]
> @@ -58,4 +73,16 @@ function M.skipcond(condition, message)
>   os.exit(test:check() and 0 or 1)
> end
> 
> +function M.tweakenv(condition, variable)
> +  if not condition or os.getenv(variable) then return end
> +  local testvar = assert(os.getenv('TEST_' .. variable),
> +                         ('Neither %s nor auxiliary TEST_%s variables are set')
> +                         :format(variable, variable))
> +  -- XXX: The third argument of setenv(3) is set to zero to forbid
> +  -- overwriting the <variable>. Since there is the check above
> +  -- whether this <variable> is set in the process environment, it
> +  -- just makes this solution foolproof.

So, if you check the presence there’s no need in third argument and os.setenv()
can be used? This can be done instead of comment above - and I’m fine. 

> +  ffi.C.setenv(variable, testvar, 0)
> +end
> +
> return M
> -- 
> 2.25.0
> 



More information about the Tarantool-patches mailing list