/* Copyright (c) 2023 Contrast Security, Inc. See * https://www.contrastsecurity.com/enduser-terms-0317a for more details. */ #include "cs__assess_hash.h" #include "../cs__common/cs__common.h" #include /* Hashes can be constructed thusly): * irb(main):001:0> Hash[:a, :b] * => {:a=>:b} * * This method instruments that unique bracket-construction style * of initializing a hash. */ static VALUE contrast_assess_hash_bracket_constructor(const int argc, VALUE *argv, const VALUE hash) { VALUE result; /* Array of Arrays: Hash[ [ [key, value], ... ] ] -> new_hash */ if (RB_TYPE_P(argv[0], T_ARRAY)) { int i; for (i = 0; i < argc; i++) { argv[i] = rb_funcall(hash_propagator, rb_sym_assess_hash_dup_and_freeze, 1, argv[i]); } /* Hash[ key, value, ... ] -> new_hash */ } else if (argc > 1) { int i; for (i = 0; i < argc; i += 2) { argv[i] = rb_funcall(hash_propagator, rb_sym_assess_hash_dup_and_freeze, 1, argv[i]); } } const VALUE *argv_final = argv; /* unhandled case - shouldn't need it since issue is only unfrozen * String keys * # Hash[ object ] -> new_hash */ result = rb_funcall2(hash, rb_sym_assess_hash_bracket_constructor, argc, argv_final); return result; } /* Hashes, when keyed with a string, will dup & freeze that string. * This is resource-efficient, but inconvenient for instrumentation. */ static VALUE contrast_assess_hash_bracket_set(const int argc, VALUE *argv, const VALUE hash) { VALUE result; /* Current name (assess_hash_bracket_set). * It doesn't set anything on the hash. * It takes the arg that /would/ have been the key, and preemptively * calls #dup and then #freeze, and then gives you that key. * * We intentionally don't enter Contrast scope for this patch. * #dup instruments the string, and #freeze gets the hash to accept * the key directly, without calling its own #dup/#freeze. * (That naturally happens in C-land, our instrumentation is in Ruby, * so our patches to #dup don't take effect within Hash#[]= unless we * specifically do this instrumentation. * We haven't revisited this approach since we started more extensively * hooking public C functions.) */ if (argc > 0) { argv[0] = rb_funcall(hash_propagator, rb_sym_assess_hash_dup_and_freeze, 1, argv[0]); } /* This is the underlying assignment, w/ our instrumented key. */ result = rb_funcall2(hash, rb_sym_assess_hash_bracket_equals, argc, argv); return result; } void Init_cs__assess_hash(void) { hash_propagator = rb_define_class_under(core_assess, "HashPropagator", rb_cObject); rb_sym_assess_hash_dup_and_freeze = rb_intern("cs__duplicate_and_freeze"); VALUE hash_class = rb_define_class("Hash", rb_cObject); rb_sym_assess_hash_bracket_constructor = contrast_register_singleton_patch( "Hash", "[]", contrast_assess_hash_bracket_constructor); rb_sym_assess_hash_bracket_equals = contrast_register_patch( "Hash", "[]=", contrast_assess_hash_bracket_set); }