diff options
author | Peter Zhu <[email protected]> | 2024-08-09 16:17:36 -0400 |
---|---|---|
committer | git <[email protected]> | 2024-08-09 20:28:53 +0000 |
commit | 7b7dde37f546b74f1dd15e482235fec139b80b70 (patch) | |
tree | 88b4035ef3db4baae739c03d4b3f518ba2b290ac | |
parent | 4e85b6b4c4868b6c75083743e2ea66ce0b2313ee (diff) |
[ruby/psych] Guard from memory leak in Psych::Emitter#start_document
When an exception is raised, it can leak memory in `head`. There are two
places that can leak memory:
1. `Check_Type(tuple, T_ARRAY)` can leak memory if `tuple` is not an
array.
2. `StringValue(name)` and `StringValue(value)` if they are not strings
and the call to `to_str` does not return a string.
This commit fixes these memory leaks by wrapping the code around a
rb_ensure so that the memory is freed in all cases.
The following code demonstrates the memory leak:
emitter = Psych::Emitter.new(StringIO.new)
nil_to_string_tags = [[nil, "tag:TALOS"]] + ([1] * 1000)
expected_array_tags = [1] * 1000
10.times do
1_000.times do
# Raises `no implicit conversion of nil into String`
emitter.start_document([], nil_to_string_tags, 0)
rescue TypeError
end
1_000.times do
# Raises `wrong argument type Integer (expected Array)`
emitter.start_document([], expected_array_tags, 0)
rescue TypeError
end
puts `ps -o rss= -p #{$$}`
end
Before:
47248
79728
111968
144224
176480
208896
241104
273280
305472
337664
After:
14832
15088
15344
15344
15360
15632
15632
15632
15648
15648
https://github.com/ruby/psych/commit/053af73818
-rw-r--r-- | ext/psych/psych_emitter.c | 66 |
1 files changed, 50 insertions, 16 deletions
diff --git a/ext/psych/psych_emitter.c b/ext/psych/psych_emitter.c index 110eb03133..0c5875f343 100644 --- a/ext/psych/psych_emitter.c +++ b/ext/psych/psych_emitter.c @@ -136,23 +136,29 @@ static VALUE end_stream(VALUE self) return self; } -/* call-seq: emitter.start_document(version, tags, implicit) - * - * Start a document emission with YAML +version+, +tags+, and an +implicit+ - * start. - * - * See Psych::Handler#start_document - */ -static VALUE start_document(VALUE self, VALUE version, VALUE tags, VALUE imp) +struct start_document_data { + VALUE self; + VALUE version; + VALUE tags; + VALUE imp; + + yaml_tag_directive_t * head; +}; + +static VALUE start_document_try(VALUE d) { + struct start_document_data * data = (struct start_document_data *)d; + VALUE self = data->self; + VALUE version = data->version; + VALUE tags = data->tags; + VALUE imp = data->imp; + yaml_emitter_t * emitter; - yaml_tag_directive_t * head = NULL; yaml_tag_directive_t * tail = NULL; yaml_event_t event; yaml_version_directive_t version_directive; TypedData_Get_Struct(self, yaml_emitter_t, &psych_emitter_type, emitter); - Check_Type(version, T_ARRAY); if(RARRAY_LEN(version) > 0) { @@ -171,8 +177,8 @@ static VALUE start_document(VALUE self, VALUE version, VALUE tags, VALUE imp) Check_Type(tags, T_ARRAY); len = RARRAY_LEN(tags); - head = xcalloc((size_t)len, sizeof(yaml_tag_directive_t)); - tail = head; + data->head = xcalloc((size_t)len, sizeof(yaml_tag_directive_t)); + tail = data->head; for(i = 0; i < len && i < RARRAY_LEN(tags); i++) { VALUE tuple = RARRAY_AREF(tags, i); @@ -182,9 +188,9 @@ static VALUE start_document(VALUE self, VALUE version, VALUE tags, VALUE imp) Check_Type(tuple, T_ARRAY); if(RARRAY_LEN(tuple) < 2) { - xfree(head); rb_raise(rb_eRuntimeError, "tag tuple must be of length 2"); } + name = RARRAY_AREF(tuple, 0); value = RARRAY_AREF(tuple, 1); StringValue(name); @@ -202,18 +208,46 @@ static VALUE start_document(VALUE self, VALUE version, VALUE tags, VALUE imp) yaml_document_start_event_initialize( &event, (RARRAY_LEN(version) > 0) ? &version_directive : NULL, - head, + data->head, tail, imp ? 1 : 0 ); emit(emitter, &event); - if(head) xfree(head); - return self; } +static VALUE start_document_ensure(VALUE d) +{ + struct start_document_data * data = (struct start_document_data *)d; + + xfree(data->head); + + return Qnil; +} + +/* call-seq: emitter.start_document(version, tags, implicit) + * + * Start a document emission with YAML +version+, +tags+, and an +implicit+ + * start. + * + * See Psych::Handler#start_document + */ +static VALUE start_document(VALUE self, VALUE version, VALUE tags, VALUE imp) +{ + struct start_document_data data = { + .self = self, + .version = version, + .tags = tags, + .imp = imp, + + .head = NULL, + }; + + return rb_ensure(start_document_try, (VALUE)&data, start_document_ensure, (VALUE)&data); +} + /* call-seq: emitter.end_document(implicit) * * End a document emission with an +implicit+ ending. |