Skip to content

Fix a crash on startup when ulimits are set to unlimited.#1615

Closed
cassandras-lies wants to merge 1 commit intofiroorg:masterfrom
cassandras-lies:ulimit
Closed

Fix a crash on startup when ulimits are set to unlimited.#1615
cassandras-lies wants to merge 1 commit intofiroorg:masterfrom
cassandras-lies:ulimit

Conversation

@cassandras-lies
Copy link
Copy Markdown
Contributor

@cassandras-lies cassandras-lies commented May 18, 2025

PR intention

Fix a crash on startup.

Code changes brief

If ulimit is set to unlimited, getrlimit will return a value of UINT_MAX, but it was being interpreted as an int, so this was evaluated as -1 and led to a crash on startup thinking resource limits had been hit.


Note

Medium Risk
Touches early startup logic that computes file descriptor and connection limits; incorrect type conversions could impact peer connection caps across platforms, though the change is small and localized.

Overview
Fixes a startup crash when the process file-descriptor limit is set to unlimited by switching connection/fd-limit bookkeeping in init.cpp from int to rlim_t and updating the std::min/std::max clamping casts accordingly.

Adds the needed platform-specific rlim_t definition/include (<sys/resource.h> on non-Windows; typedef on Windows) so resource-limit values returned from getrlimit aren’t misinterpreted as negative and treated as exhausted limits during initialization.

Written by Cursor Bugbot for commit a2e2da3. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2025

Walkthrough

The update changes the types of three internal initialization variables from int to rlim_t in the src/init.cpp file. All related logic and calculations in the AppInitParameterInteraction() function are updated to use rlim_t, ensuring type consistency for resource limit operations.

Changes

File(s) Change Summary
src/init.cpp Changed variable types (nMaxConnections, nUserMaxConnections, nFD) from int to rlim_t; updated related logic in AppInitParameterInteraction() to use rlim_t and appropriate casts for resource limit calculations.

Poem

Three variables grew up today,
From humble int to rlim_t they say.
Now limits are clear, resource bounds tight,
With type-safe math, all through the night.
The code hops forward, strong and neat—
A rabbit’s work is now complete! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91a9fb5 and a2e2da3.

📒 Files selected for processing (1)
  • src/init.cpp (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/init.cpp
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Build for Linux (Autotools)
  • GitHub Check: Build for Windows (Autotools)
  • GitHub Check: Build for macOS (Autotools)
  • GitHub Check: Build for Linux (CMake)
  • GitHub Check: Build for macOS (CMake)
  • GitHub Check: Build for Windows (CMake)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@aleflm aleflm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

Just a suggestion, how about using uint64_t instead of introducing rlim_t.

@cassandras-lies
Copy link
Copy Markdown
Contributor Author

The return parameter is defined to be rlim_t, which is not necessarily uint64_t, it could be an int or something depending on the platform. I defined it as an unsigned long long on Windows in the #else directive.

@reubenyap reubenyap requested a review from aleflm June 26, 2025 14:58
@reubenyap
Copy link
Copy Markdown
Member

bugbot run

@reubenyap
Copy link
Copy Markdown
Member

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Comment thread src/init.cpp

// Trim requested connection counts, to fit into system limitations
nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS)), 0);
nMaxConnections = std::max(std::min(nMaxConnections, (rlim_t)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS)), (rlim_t)0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Format specifier mismatch for rlim_t type variables

Medium Severity

The variables nMaxConnections, nUserMaxConnections, and nFD were changed from int to rlim_t (defined as unsigned long long on Windows), but the strprintf call at line 1183 still uses %d format specifiers which expect int. This format mismatch causes undefined behavior and will print garbage or incorrect values in the warning message. There's also a similar issue at line 1462 using %i with these variables in LogPrintf.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Member

@reubenyap reubenyap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1615 Review: Fix crash on startup when ulimits are set to unlimited

[1 file changed, +8 / -6] — Changes nMaxConnections, nUserMaxConnections, and nFD from int to rlim_t to fix a crash when getrlimit returns RLIM_INFINITY (interpreted as -1 when stored in int).

Static Analysis

cppcheck: No new issues in changed code (pre-existing warnings only). flawfinder: No new hits. semgrep (Firo rules): 38 pre-existing hits, none in changed lines.

Findings Summary

# Severity File Finding
1 HIGH src/init.cpp:119 #define rlim_t is a dangerous macro redefining a POSIX type
2 HIGH src/init.cpp:1075 rlim_t variables silently narrow when passed to int consumers
3 MEDIUM src/init.cpp:1172 Signed GetArg return wraps to huge unsigned in rlim_t
4 LOW src/init.cpp:121 System include buried inside preprocessor block

Architectural Concern

The root cause of the original crash is in RaiseFileDescriptorLimit() (src/util.cpp:771) which returns rlim_t as int, truncating RLIM_INFINITY to -1. A more targeted fix would be to fix RaiseFileDescriptorLimit itself to clamp rlim_cur to INT_MAX before returning, keeping all the downstream variables as int. This avoids cascading type changes and the narrowing conversion issues identified above.

Each finding is posted as an inline comment on the relevant code — click to view, resolve when addressed. Each comment includes a Prompt for AI agents that can be copy-pasted into Cursor/Copilot/Claude Code to auto-fix the issue.


Review assisted by AI + static analysis (cppcheck, flawfinder, semgrep). Posted by @reubenyap.

Comment thread src/init.cpp
// accessing block files don't count towards the fd_set size limit
// anyway.
#define MIN_CORE_FILEDESCRIPTORS 0
#define rlim_t unsigned long long
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] #define rlim_t unsigned long long is a dangerous macro that redefines a POSIX type

Using #define to create a type alias is fragile. Unlike typedef, a #define doesn't respect scope, can clash with any header that defines or uses rlim_t, and expands textually (e.g. (rlim_t)x becomes (unsigned long long)x — works by luck but is not a proper cast expression in all contexts). On non-Windows, <sys/resource.h> already provides rlim_t.

Suggested fix:
Use typedef instead:

#ifdef WIN32
#define MIN_CORE_FILEDESCRIPTORS 0
typedef unsigned long long rlim_t;
#else
#include <sys/resource.h>
#define MIN_CORE_FILEDESCRIPTORS 150
#endif
🤖 Prompt for AI agents

"In file src/init.cpp, around line 119, #define rlim_t unsigned long long is used inside the #ifdef WIN32 block. Replace it with typedef unsigned long long rlim_t;. Keep the #define MIN_CORE_FILEDESCRIPTORS 0 on the line before it. Keep the #else branch with #include <sys/resource.h> unchanged."

Comment thread src/init.cpp
int nMaxConnections;
int nUserMaxConnections;
int nFD;
rlim_t nMaxConnections;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] nMaxConnections is rlim_t but downstream consumers expect int

Changing these variables to rlim_t (unsigned long long) introduces implicit narrowing conversions at several downstream sites:

  • Line 2079: connOptions.nMaxConnections = nMaxConnections;CConnman::Options::nMaxConnections is int (net.h:165)
  • Line 2080: connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections);
  • Line 1177: RaiseFileDescriptorLimit(...) takes int and returns int (util.h:121)
  • Lines 1183/1462: %d/%i format specifiers used with rlim_t values — this is undefined behavior

The real fix should keep nMaxConnections/nFD as int but fix the narrowing at the getrlimit call site in RaiseFileDescriptorLimit(). Or if rlim_t is kept here, all downstream consumers must also be updated.

Suggested fix:
The safer approach: keep these as int, and fix RaiseFileDescriptorLimit to internally handle rlim_t from getrlimit and clamp to INT_MAX before returning. This avoids cascading type changes throughout the codebase.

🤖 Prompt for AI agents

"In file src/init.cpp, lines 1075-1077 change nMaxConnections, nUserMaxConnections, and nFD from int to rlim_t. This causes implicit narrowing conversions downstream: CConnman::Options::nMaxConnections is int (src/net.h:165), RaiseFileDescriptorLimit takes and returns int (src/util.h:121), and format specifiers %d/%i at lines 1183 and 1462 expect int not unsigned long long. Either revert these to int and fix the root cause in RaiseFileDescriptorLimit() in src/util.cpp (line 771-788) to internally use rlim_t from getrlimit but clamp the return value to INT_MAX, or update all downstream consumers to accept rlim_t. The former approach is safer and more localized."

Comment thread src/init.cpp
@@ -1168,10 +1170,10 @@ bool AppInitParameterInteraction()
(mapMultiArgs.count("-bind") ? mapMultiArgs.at("-bind").size() : 0) +
(mapMultiArgs.count("-whitebind") ? mapMultiArgs.at("-whitebind").size() : 0), size_t(1));
nUserMaxConnections = GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] GetArg returns signed int64_t, stored in unsigned rlim_t — negative values wrap

GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS) returns int64_t. If a user passes -maxconnections=-1, the signed value -1 is stored into unsigned rlim_t and becomes ULLONG_MAX (~1.8×10¹⁹). The old code used int, so std::max(nUserMaxConnections, 0) on line 1173 would clamp it to 0. With rlim_t, std::max(nUserMaxConnections, (rlim_t)0) compares two unsigned values, and the wrapped value is enormous, so the clamp has no effect.

Suggested fix:
Clamp the signed value before converting to unsigned:

int64_t rawMaxConn = GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
nUserMaxConnections = (rawMaxConn < 0) ? 0 : static_cast<rlim_t>(rawMaxConn);
🤖 Prompt for AI agents

"In file src/init.cpp, line 1172, nUserMaxConnections = GetArg(\"-maxconnections\", DEFAULT_MAX_PEER_CONNECTIONS); stores a signed int64_t return value in unsigned rlim_t, causing negative values to wrap to huge unsigned numbers. Add an intermediate signed variable and clamp: int64_t rawMaxConn = GetArg(\"-maxconnections\", DEFAULT_MAX_PEER_CONNECTIONS); nUserMaxConnections = (rawMaxConn < 0) ? 0 : static_cast<rlim_t>(rawMaxConn);. Remove the now-redundant std::max(nUserMaxConnections, (rlim_t)0) on line 1173."

Comment thread src/init.cpp
#define MIN_CORE_FILEDESCRIPTORS 0
#define rlim_t unsigned long long
#else
#include <sys/resource.h>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] <sys/resource.h> include is buried inside a preprocessor block

Placing #include <sys/resource.h> inside the #else (non-Windows) block at line 121 is unconventional. System includes are typically grouped at the top of the file for discoverability. While functionally correct, this makes it easy to miss during code review.

Suggested fix:
Move the include to the system includes section at the top of the file, wrapped in #ifndef WIN32:

#ifndef WIN32
#include <sys/resource.h>
#endif
🤖 Prompt for AI agents

"In file src/init.cpp, line 121, #include <sys/resource.h> is placed inside the #ifdef WIN32 / #else block around line 114-123. Move it to the system includes section near the top of the file (around lines 30-50), wrapped in #ifndef WIN32 / #endif. Remove it from its current location inside the preprocessor block. Keep the #define MIN_CORE_FILEDESCRIPTORS 150 in the #else branch."

@reubenyap
Copy link
Copy Markdown
Member

🤖 Combined AI Fix Prompt

Verify each finding against the current code and only fix it if needed.

  1. src/init.cpp — Replace #define rlim_t with typedef: In the Windows preprocessor block near the top of init.cpp, replace #define rlim_t unsigned long long with typedef unsigned long long rlim_t;. This prevents macro-related bugs where the preprocessor blindly substitutes text (e.g. inside casts or template arguments) and follows the C++ convention for type aliases.

  2. src/init.cpp — Add safe narrowing for downstream int consumers: After the lines that compute nMaxConnections and nFD as rlim_t, add range-clamping before they are passed to APIs that expect int. For example, before assigning to connOptions.nMaxConnections, add: if (nMaxConnections > static_cast<rlim_t>(std::numeric_limits<int>::max())) nMaxConnections = std::numeric_limits<int>::max();. Do the same for nFD before it is passed to RaiseFileDescriptorLimit(). Add #include <limits> if not already present. Also update the LogPrintf format specifiers from %d/%i to %llu (or use PRIu64 / stream output) for all lines that print nMaxConnections, nUserMaxConnections, or nFD.

  3. src/init.cpp — Guard GetArg against negative values: After rlim_t nUserMaxConnections = GetArg("-maxconnections", ...), add a check: if (GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS) < 0) { return InitError("..."); } (or clamp to 0). This prevents a negative user-supplied value from wrapping to a huge unsigned number when stored in a rlim_t.

  4. src/init.cpp — Move <sys/resource.h> include to top-level block: Move #include <sys/resource.h> out of the #ifndef WIN32 block near the rlim_t definition and place it with the other system includes at the top of the file (still guarded by #ifndef WIN32). This improves discoverability and follows the project convention of grouping system headers together.

@reubenyap
Copy link
Copy Markdown
Member

Correction / Clarification on the Architectural Concern

I flagged that the root cause of the crash is in RaiseFileDescriptorLimit() (src/util.cpp:771) but didn't include a concrete fix for it. Here's the full analysis:

Why the crash happens (on master):

  1. When ulimits are unlimited, getrlimit() returns rlim_cur = RLIM_INFINITY (~0xFFFFFFFFFFFFFFFF)
  2. RaiseFileDescriptorLimit returns this as int, truncating to -1
  3. nFD = -1, then nFD < MIN_CORE_FILEDESCRIPTORS-1 < 150 → init error / crash

How this PR fixes it (indirectly):
By changing nFD from int to rlim_t, the -1 returned by RaiseFileDescriptorLimit wraps to ULLONG_MAX via signed-to-unsigned conversion. This makes the < 150 check pass. It works, but relies on unsigned wraparound rather than fixing the truncation at the source.

Recommended targeted fix — change RaiseFileDescriptorLimit itself:

In src/util.h, change the declaration:

// Before:
int RaiseFileDescriptorLimit(int nMinFD);
// After:
int RaiseFileDescriptorLimit(int nMinFD);  // no change needed here if we clamp inside

In src/util.cpp (line ~784), clamp before returning:

int RaiseFileDescriptorLimit(int nMinFD) {
#if defined(WIN32)
    return 2048;
#else
    struct rlimit limitFD;
    if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) {
        if (limitFD.rlim_cur < (rlim_t)nMinFD) {
            limitFD.rlim_cur = nMinFD;
            if (limitFD.rlim_cur > limitFD.rlim_max)
                limitFD.rlim_cur = limitFD.rlim_max;
            setrlimit(RLIMIT_NOFILE, &limitFD);
            getrlimit(RLIMIT_NOFILE, &limitFD);
        }
        // Clamp to INT_MAX to avoid truncation when rlim_cur is RLIM_INFINITY
        if (limitFD.rlim_cur > (rlim_t)std::numeric_limits<int>::max())
            return std::numeric_limits<int>::max();
        return limitFD.rlim_cur;
    }
    return nMinFD; // getrlimit failed, assume it's fine
#endif
}

This approach fixes the crash at the source without requiring any type changes in init.cpp. The rlim_t changes in init.cpp are then optional (still fine to keep, but the crash is already prevented). Requires #include <limits> in util.cpp if not already present.

@reubenyap
Copy link
Copy Markdown
Member

Superseded by #1810

@reubenyap reubenyap closed this Mar 24, 2026
reubenyap added a commit that referenced this pull request Mar 24, 2026
When ulimits are set to unlimited, getrlimit() returns rlim_cur as
RLIM_INFINITY (~0xFFFFFFFFFFFFFFFF). Since RaiseFileDescriptorLimit()
returns int, this value is silently truncated to -1, causing a
spurious "Not enough file descriptors available" init error.

Clamp the return value to INT_MAX before the implicit narrowing
conversion. This fixes the root cause at the source rather than
requiring callers to change their variable types.

See: #1615

Co-authored-by: Reuben Yap <reuben.yap@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants