ruby/ruby · critical
RUBY_DEBUG_LOG=mem is not specified.
Error message
RUBY_DEBUG_LOG=mem is not specified.
What it means
Raised by Gem::Installer#verify_spec (second bindir check) when File.expand_path(File.join(gem_dir, spec.bindir)) falls outside the expanded gem directory — i.e. bindir contains '..' segments or another traversal that escapes the install root. This blocks path-traversal attacks where a crafted gem writes executables outside its own directory. A legitimate bindir like 'bin' or 'exe' can never trip it.
Source
Thrown at debug.c:687
static void
debug_log_dump(FILE *out, unsigned int n)
{
if (ruby_debug_log_mode & ruby_debug_log_memory) {
unsigned int size = debug_log.cnt > MAX_DEBUG_LOG ? MAX_DEBUG_LOG : debug_log.cnt;
unsigned int current_index = debug_log.cnt % MAX_DEBUG_LOG;
if (n == 0) n = size;
if (n > size) n = size;
for (unsigned int i=0; i<n; i++) {
int index = current_index - size + i;
if (index < 0) index += MAX_DEBUG_LOG;
VM_ASSERT(index <= MAX_DEBUG_LOG);
const char *mesg = RUBY_DEBUG_LOG_MEM_ENTRY(index);
fprintf(out, "%4u: %s\n", debug_log.cnt - size + i, mesg);
}
}
else {
fprintf(stderr, "RUBY_DEBUG_LOG=mem is not specified.");
}
}
// for debuggers
void
ruby_debug_log_print(unsigned int n)
{
debug_log_dump(stderr, n);
}
void
ruby_debug_log_dump(const char *fname, unsigned int n)
{
FILE *fp = fopen(fname, "w");
if (fp == NULL) {
fprintf(stderr, "can't open %s. give up.\n", fname);
}View on GitHub (pinned to 0e5b888e1c)
Solutions
- Use a plain in-gem directory: spec.bindir = 'bin' (or 'exe') and rebuild
- Audit any gemspec template that interpolates paths into bindir
- If the gem is third-party and contains traversal bindir, treat it as malicious and report/remove it
Example fix
# before spec.bindir = '../bin' # after spec.bindir = 'bin'
Defensive patterns
Strategy: validation
Validate before calling
expanded = File.expand_path(File.join(gem_dir, spec.bindir)) inside = expanded == gem_dir || expanded.start_with?(gem_dir + '/') abort 'bindir escapes gem dir' unless inside
Type guard
->(s, dir) { e = File.expand_path(File.join(dir, s.bindir)); e == File.expand_path(dir) || e.start_with?(File.expand_path(dir) + '/') } Try / catch
begin
Gem::Installer.new(path).install
rescue Gem::InstallError => e
raise SecurityError, "possible traversal gem: #{e.message}" if e.message.include?('bindir')
end Prevention
- Never template external paths into bindir; use a constant in-gem directory
- Quarantine any third-party gem whose metadata fails installer verification
When it happens
Trigger: Installing a gem whose spec has bindir = '../bin', bindir = 'bin/../../share/bin', or any value whose expansion is not gem_dir or under gem_dir + '/'. Constructed maliciously, or accidentally when a build script interpolates a computed path like "#{ENV['HOME']}/bin" into bindir.
Common situations: Malicious gems planted on mirrors or vendored paths attempting to overwrite system executables; automation that generates gemspecs from templates with unescaped relative paths; gems assembled by hand with `tar` and a custom spec.
Related errors
- missing codepage argument
- unexpected debug option: %.*s\n
- too long: %s (max:%d)\n
- setup_debug_log failed (can't allocate memory)\n
- can not parse RUBY_DEBUG_LOG filename: %s\n
AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21).
Data as JSON: /api/errors/4b5b5b431a58a229.
Report an issue: GitHub.