diff --git a/bin/verilator b/bin/verilator index 78d1088f5..5fefa73bd 100755 --- a/bin/verilator +++ b/bin/verilator @@ -27,6 +27,21 @@ use vars qw($Debug @Opt_Verilator_Sw); autoflush STDOUT 1; autoflush STDERR 1; +# Guard against unbounded recursive re-entry. Hierarchical / --build flows +# legitimately re-enter the wrapper a few levels deep (wrapper -> verilator_bin +# -> make -> wrapper -> ...); a misconfigured VERILATOR_BIN, VERILATOR_GDB or +# VERILATOR_VALGRIND that points back at this wrapper, however, recurses +# without bound and exhausts the OS PID table. Use VERILATOR_RUNNING as a +# depth counter and cap it well above any real nesting. +my $verilator_running_max = 16; +my $verilator_running_depth = ($ENV{VERILATOR_RUNNING} || 0) + 1; +if ($verilator_running_depth > $verilator_running_max) { + die "%Error: verilator: re-entered $verilator_running_depth levels deep" + . " via \$VERILATOR_RUNNING; check that VERILATOR_BIN, VERILATOR_GDB" + . " and VERILATOR_VALGRIND do not resolve back to this wrapper script.\n"; +} +$ENV{VERILATOR_RUNNING} = $verilator_running_depth; + $Debug = 0; my $opt_aslr; my $opt_gdb; diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 3a41ed65f..fc824f1c8 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -165,6 +165,7 @@ Liam Braun Luca Colagrande Ludwig Rogiers Lukasz Dalek +M2kar Maarten De Braekeleer Maciej Sobkowski Marcel Chang diff --git a/test_regress/t/t_flag_verilator_running.py b/test_regress/t/t_flag_verilator_running.py new file mode 100644 index 000000000..1369b9cad --- /dev/null +++ b/test_regress/t/t_flag_verilator_running.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('dist') + +# Real-world repro: a misconfigured VERILATOR_BIN that points back at the +# Perl wrapper used to fork-bomb the host. The wrapper now caps re-entry +# depth via $VERILATOR_RUNNING and aborts past the cap. +os.environ['VERILATOR_BIN'] = os.environ["VERILATOR_ROOT"] + "/bin/verilator" + +# --no-unlimited-stack avoids the ulimit_stack_unlimited() backtick branch, +# which would otherwise double the recursion fanout. Linear re-entry through +# run() is enough to exercise the depth-cap abort. +test.run(fails=True, + cmd=[os.environ["VERILATOR_ROOT"] + "/bin/verilator", + "--no-unlimited-stack", "--version"], + logfile=test.run_log_filename) + +test.file_grep(test.run_log_filename, + r'%Error: verilator: re-entered \d+ levels deep via \$VERILATOR_RUNNING') + +test.passes()