CI/CD inputs: truncate() with a very large argument raises RangeError
Summary
The truncate(<offset>,<length>) interpolation function for CI/CD inputs accepts any run of digits for both arguments (truncate.rb#L10), converts them with to_i (truncate.rb#L28-34) and calls input_value[offset, length] (truncate.rb#L19). When either argument does not fit in a C long (2**63 or more on 64-bit Linux), String#[] raises RangeError because it cannot convert the Integer into a C long.
Nothing on the way up rescues RangeError: it is not in Config::RESCUE_ERRORS, nor in the rescues of expand_config or YamlProcessor#execute. So instead of a validation message, the static validation of the CI lint API answers 500, and the lint dry run and pipeline creation fall into the catch-all of Chain::Config::Process, which reports Undefined error (<correlation id>) and sends the exception to error tracking.
Steps to reproduce
-
In any project, call
POST /projects/:id/ci/lintwith this configuration:spec: inputs: s: default: abcdef --- a: script: echo "$[[ inputs.s | truncate(0,99999999999999999999) ]]" -
Compare with
truncate(0,2), which givesscript: echo "ab".
Example Project
Not needed: the configuration above is enough.
What is the current bug behavior?
The static validation of the CI lint API answers 500 Internal Server Error. The lint dry run answers valid: false with Undefined error (<correlation id>), and creating a pipeline from a .gitlab-ci.yml with this configuration fails with the same Undefined error.
I saw this on a self-managed GitLab EE 17.6.1, with the configuration above:
--- POST /ci/lint
{"message":"500 Internal Server Error"} HTTP 500
--- POST /ci/lint dry_run
{"valid":false,"errors":["Undefined error (01M4QTTKXCZKWWBAMY9PHG1YET)"],"warnings":[],"merged_yaml":null,"includes":null} HTTP 200
--- control: truncate(0,2)
{"valid":true,"errors":[],"warnings":[],"merged_yaml":"---\na:\n script: echo \"ab\"\n","includes":[]} HTTP 200
--- POST /pipeline
{"message":{"base":["Undefined error (01M4QTTN9K8M9TRW2NWMN1Z67X)"]}} HTTP 400On master at 714fe569 the function is the same and none of the rescues linked above catches RangeError. In the spec suite there, Gitlab::Ci::Lint#legacy_validate, which POST /projects/:id/ci/lint calls, raises the same RangeError for this configuration, its dry run and Ci::CreatePipelineService end in Undefined error (<correlation id>), and the unit spec below fails the same way.
What is the expected correct behavior?
The function returns the same result as for any other value past the end of the input (truncate(0,99999999999999999999) gives abcdef, as truncate(0,100) does), or at worst a normal configuration error that names the function call.
Relevant logs and/or screenshots
From api_json.log on 17.6.1, for the 500:
RangeError | bignum too big to convert into `long'
lib/gitlab/ci/config/interpolation/functions/truncate.rb:19:in `[]'
lib/gitlab/ci/config/interpolation/functions/truncate.rb:19:in `execute'
lib/gitlab/ci/config/interpolation/functions_stack.rb:41:in `block in evaluate'
lib/gitlab/ci/config/interpolation/functions_stack.rb:38:in `each'
lib/gitlab/ci/config/interpolation/functions_stack.rb:38:in `reduce'
lib/gitlab/ci/config/interpolation/functions_stack.rb:38:in `evaluate'On master with Ruby 3.3.11, a unit spec that calls the function with truncate(0,99999999999999999999):
Failure/Error: input_value[offset, length].to_s
RangeError:
bignum too big to convert into `long'
# ./lib/gitlab/ci/config/interpolation/functions/truncate.rb:19:in `[]'
# ./lib/gitlab/ci/config/interpolation/functions/truncate.rb:19:in `execute'Output of checks
Not from an installation check: the 17.6.1 output above comes from the API and api_json.log, and the master output from the spec suite.
Possible fixes
Clamp both arguments to the input length before slicing in Functions::Truncate#execute. No argument past the end of the input changes the result, so every value that works today keeps its result. A validation error with an upper limit would also stop the exception, but the documentation of truncate sets no maximum, and a portable limit such as 2**31 - 1 would reject values that work today.