symbol.c: add dynamic symbol limit (MRB_SYMBOL_MAX)

track dynamic (runtime-created) symbols separately from presyms,
inline symbols, and static C API symbols. raise RuntimeError when
the dynamic symbol count exceeds MRB_SYMBOL_MAX (default 4096).

this prevents DoS attacks via unbounded symbol creation (e.g.
"str".to_sym in a loop). presyms and inline symbols are not
counted toward the limit.

infrastructure for future symbol GC: sym_flags array tracks
per-symbol metadata (SYM_FL_DYNAMIC flag).

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-03-18 23:48:04 +09:00
parent 3f8e13da6f
commit afc0753c1d
3 changed files with 29 additions and 0 deletions
+7
View File
@@ -172,6 +172,13 @@
#define MRB_SYMBOL_LINEAR_THRESHOLD 256
#endif
/* Maximum number of dynamic symbols (created at runtime via to_sym etc.)
Presyms, inline symbols, and mrb_intern_static symbols are excluded.
Set to 0 to disable the limit. */
#ifndef MRB_SYMBOL_MAX
#define MRB_SYMBOL_MAX 4096
#endif
/* obsolete configurations */
#if defined(DISABLE_STDIO) || defined(MRB_DISABLE_STDIO)
# define MRB_NO_STDIO
+2
View File
@@ -320,9 +320,11 @@ struct mrb_state {
mrb_sym symidx;
const char **symtbl;
uint8_t *sym_flags; /* per-symbol flags (SYM_FL_*) */
size_t symcapa;
struct mrb_sym_hash_table *symhash;
void *sym_pool;
mrb_sym dynamic_sym_count; /* count of dynamic (GC-candidate) symbols */
#ifndef MRB_USE_ALL_SYMBOLS
char symbuf[8]; /* buffer for small symbol names */
#endif
+20
View File
@@ -48,6 +48,10 @@ presym_sym2name(mrb_sym sym, mrb_int *lenp)
/* ------------------------------------------------------ */
/* Per-symbol flags (stored in mrb->sym_flags[]) */
#define SYM_FL_DYNAMIC 0x01 /* created at runtime (to_sym, send, etc.) */
#define SYM_FL_MARK 0x02 /* marked during symbol GC (reserved for future) */
/* LSB pointer tagging for literal flags */
#define SYMTBL_LITERAL_FLAG ((uintptr_t)1)
@@ -318,6 +322,8 @@ sym_intern_common(mrb_state *mrb, const char *name, size_t len, mrb_bool lit)
if (symcapa == 0) symcapa = 100;
else symcapa = (size_t)(symcapa * 6 / 5);
mrb->symtbl = (const char**)mrb_realloc(mrb, (void*)mrb->symtbl, sizeof(char*)*symcapa);
mrb->sym_flags = (uint8_t*)mrb_realloc(mrb, mrb->sym_flags, symcapa);
memset(mrb->sym_flags + mrb->symcapa, 0, symcapa - mrb->symcapa);
if (using_hash_table(mrb)) {
struct mrb_sym_hash_table *ht = mrb->symhash;
ht->symlink = (uint8_t*)mrb_realloc(mrb, ht->symlink, symcapa);
@@ -346,6 +352,13 @@ sym_intern_common(mrb_state *mrb, const char *name, size_t len, mrb_bool lit)
}
mrb->symidx = sym;
if (!lit) {
mrb->sym_flags[sym] = SYM_FL_DYNAMIC;
mrb->dynamic_sym_count++;
}
else {
mrb->sym_flags[sym] = 0;
}
return sym;
}
@@ -387,6 +400,12 @@ sym_intern(mrb_state *mrb, const char *name, size_t len, mrb_bool lit)
sym = find_symbol(mrb, name, len, NULL);
if (sym > 0) return sym;
#if MRB_SYMBOL_MAX > 0
if (!lit && mrb->dynamic_sym_count >= MRB_SYMBOL_MAX) {
mrb_raise(mrb, E_RUNTIME_ERROR, "symbol table overflow");
}
#endif
/* Check if we need to migrate to hash table */
if (!using_hash_table(mrb) && mrb->symidx >= MRB_SYMBOL_LINEAR_THRESHOLD) {
migrate_to_hash_table(mrb);
@@ -634,6 +653,7 @@ mrb_free_symtbl(mrb_state *mrb)
mrb->sym_pool = NULL;
mrb_free(mrb, (void*)mrb->symtbl);
mrb_free(mrb, mrb->sym_flags);
/* Free hash table if allocated */
if (mrb->symhash) {