Merge bitcoin/bitcoin#33243: test: Fix CLI_MAX_ARG_SIZE issues

fa96a4afea2a9bf90c843198e75a00acef02c32d ci: Enable CI_LIMIT_STACK_SIZE=1 in i686_no_ipc task (MarcoFalke)
facfde2cdce661c10be3254a6be99af49ceee072 test: Fix CLI_MAX_ARG_SIZE issues (MarcoFalke)

Pull request description:

  `CLI_MAX_ARG_SIZE` has many edge case issues:

  * It seems to be lower on some systems, but it is unknown how to reproduce locally: https://github.com/bitcoin/bitcoin/pull/33079#issuecomment-3139957274
  * `MAX_ARG_STRLEN` is a limit per arg, but we probably want "The maximum length of [all of] the arguments": See https://www.man7.org/linux/man-pages/man3/sysconf.3.html, section `ARG_MAX - _SC_ARG_MAX`.
  * It doesn't account for the additional args added by the `bitcoin` command later on: 73220fc0f9/src/bitcoin.cpp (L85-L92)
  * It doesn't account for unicode encoding a string to bytes before taking its length.

  The issues are mostly harmless edge cases, but it would be good to fix them. So do that here, by:

  * Replacing `max()` by `sum()`, to correctly take into account all args, not just the largest one.
  * Reduce `CLI_MAX_ARG_SIZE`, to account for the `bitcoin` command additional args.

  Also, there is a test. The test can be called with `ulimit` to hopefully limit the max args size to the hard-coded value in the test framework. For reference:

  ```
  $ ( ulimit -s 512 && python3 -c 'import os; print(os.sysconf("SC_ARG_MAX") )' )
  131072
  ```

  On top of this pull it should pass, ...

  ```
  bash -c 'ulimit -s 512 && BITCOIN_CMD="bitcoin -M" ./bld-cmake/test/functional/rpc_misc.py --usecli -l DEBUG'
  ```

  ... and with the test_framework changes reverted, it should fail:

  ```
  OSError: [Errno 7] Argument list too long: 'bitcoin'
  ```

  Also, there is a commit to enable `CI_LIMIT_STACK_SIZE=1` in the i686 task, because it should now be possible and no longer hit the hard-to-reproduce issue mentioned above.

ACKs for top commit:
  cedwies:
    ACK fa96a4a
  achow101:
    ACK fa96a4afea2a9bf90c843198e75a00acef02c32d
  enirox001:
    ACK fa96a4a — thanks for addressing the nits and clarifying the test; LGTM.
  mzumsande:
    Code Review ACK fa96a4afea2a9bf90c843198e75a00acef02c32d

Tree-SHA512: d12211bd097d692d560c3615970ec0e911707d8c6cbbb145591abc548beed55f487a80b08f0a8c89d4eef4d76a9fbd6a33edc0b42b5860a93dd7b954355bc887
This commit is contained in:
Ava Chow 2025-09-11 15:37:48 -07:00
commit b81445333a
No known key found for this signature in database
GPG Key ID: 17565732E08E5E41
3 changed files with 21 additions and 4 deletions

View File

@ -13,6 +13,7 @@ export CI_IMAGE_PLATFORM="linux/amd64"
export PACKAGES="llvm clang g++-multilib"
export DEP_OPTS="DEBUG=1 NO_IPC=1"
export GOAL="install"
export CI_LIMIT_STACK_SIZE=1
export TEST_RUNNER_EXTRA="--v2transport --usecli"
export BITCOIN_CONFIG="\
-DCMAKE_BUILD_TYPE=Debug \

View File

@ -30,6 +30,13 @@ class RpcMiscTest(BitcoinTestFramework):
lambda: node.echo(arg9='trigger_internal_bug'),
)
self.log.info("test max arg size")
ARG_SZ_COMMON = 131071 # Common limit, used previously in the test framework, serves as a regression test
ARG_SZ_LARGE = 8 * 1024 * 1024 # A large size, which should be rare to hit in practice
for arg_sz in [0, 1, 100, ARG_SZ_COMMON, ARG_SZ_LARGE]:
arg_string = 'a' * arg_sz
assert_equal([arg_string, arg_string], node.echo(arg_string, arg_string))
self.log.info("test getmemoryinfo")
memory = node.getmemoryinfo()['locked']
assert_greater_than(memory['used'], 0)

View File

@ -48,7 +48,12 @@ BITCOIND_PROC_WAIT_TIMEOUT = 60
# The size of the blocks xor key
# from InitBlocksdirXorKey::xor_key.size()
NUM_XOR_BYTES = 8
CLI_MAX_ARG_SIZE = 131071 # many systems have a 128kb limit per arg (MAX_ARG_STRLEN)
# Many systems have a 128kB limit for a command size. Depending on the
# platform, this limit may be larger or smaller. Moreover, when using the
# 'bitcoin' command, it may internally insert more args, which must be
# accounted for. There is no need to pick the largest possible value here
# anyway and it should be fine to set it to 1kB in tests.
TEST_CLI_MAX_ARG_SIZE = 1024
# The null blocks key (all 0s)
NULL_BLK_XOR_KEY = bytes([0] * NUM_XOR_BYTES)
@ -951,10 +956,14 @@ class TestNodeCLI():
if clicommand is not None:
p_args += [clicommand]
p_args += pos_args + named_args
max_arg_size = max(len(arg) for arg in p_args)
# TEST_CLI_MAX_ARG_SIZE is set low enough that checking the string
# length is enough and encoding to bytes is not needed before
# calculating the sum.
sum_arg_size = sum(len(arg) for arg in p_args)
stdin_data = self.input
if max_arg_size > CLI_MAX_ARG_SIZE:
self.log.debug(f"Cli: Command size {max_arg_size} too large, using stdin")
if sum_arg_size >= TEST_CLI_MAX_ARG_SIZE:
self.log.debug(f"Cli: Command size {sum_arg_size} too large, using stdin")
rpc_args = "\n".join([arg for arg in p_args[base_arg_pos:]])
if stdin_data is not None:
stdin_data += "\n" + rpc_args