{"record":{"id":"4b5b5b431a58a229","repo":"ruby/ruby","slug":"ruby-debug-log-mem-is-not-specified","errorCode":null,"errorMessage":"RUBY_DEBUG_LOG=mem is not specified.","messagePattern":"RUBY_DEBUG_LOG=mem is not specified\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"debug.c","lineNumber":687,"sourceCode":"static void\ndebug_log_dump(FILE *out, unsigned int n)\n{\n    if (ruby_debug_log_mode & ruby_debug_log_memory) {\n        unsigned int size = debug_log.cnt > MAX_DEBUG_LOG ? MAX_DEBUG_LOG : debug_log.cnt;\n        unsigned int current_index = debug_log.cnt % MAX_DEBUG_LOG;\n        if (n == 0) n = size;\n        if (n > size) n = size;\n\n        for (unsigned int i=0; i<n; i++) {\n            int index = current_index - size + i;\n            if (index < 0) index += MAX_DEBUG_LOG;\n            VM_ASSERT(index <= MAX_DEBUG_LOG);\n            const char *mesg = RUBY_DEBUG_LOG_MEM_ENTRY(index);\n            fprintf(out, \"%4u: %s\\n\", debug_log.cnt - size + i, mesg);\n        }\n    }\n    else {\n        fprintf(stderr, \"RUBY_DEBUG_LOG=mem is not specified.\");\n    }\n}\n\n// for debuggers\n\nvoid\nruby_debug_log_print(unsigned int n)\n{\n    debug_log_dump(stderr, n);\n}\n\nvoid\nruby_debug_log_dump(const char *fname, unsigned int n)\n{\n    FILE *fp = fopen(fname, \"w\");\n    if (fp == NULL) {\n        fprintf(stderr, \"can't open %s. give up.\\n\", fname);\n    }","sourceCodeStart":669,"sourceCodeEnd":705,"githubUrl":"https://github.com/ruby/ruby/blob/0e5b888e1c355f3f728f2659f085820937dada48/debug.c#L669-L705","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nspec.bindir = '../bin'\n# after\nspec.bindir = 'bin'","handlingStrategy":"validation","validationCode":"expanded = File.expand_path(File.join(gem_dir, spec.bindir))\ninside = expanded == gem_dir || expanded.start_with?(gem_dir + '/')\nabort 'bindir escapes gem dir' unless inside","typeGuard":"->(s, dir) { e = File.expand_path(File.join(dir, s.bindir)); e == File.expand_path(dir) || e.start_with?(File.expand_path(dir) + '/') }","tryCatchPattern":"begin\n  Gem::Installer.new(path).install\nrescue Gem::InstallError => e\n  raise SecurityError, \"possible traversal gem: #{e.message}\" if e.message.include?('bindir')\nend","preventionTips":["Never template external paths into bindir; use a constant in-gem directory","Quarantine any third-party gem whose metadata fails installer verification"],"tags":["rubygems","gemspec","path-traversal","security","install"],"backgroundTag":"path-traversal-blocked","analyzedSha":"0e5b888e1c355f3f728f2659f085820937dada48","analyzedAt":"2026-08-21T14:25:43.473Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}