Skip to content
Browse files

added stack protection

  • Loading branch information...
1 parent 306bca4 commit 9d0c45a7adb908a49f8c87f4f2c02c54c6a70d8f @ohler55 committed Oct 4, 2012
Showing with 40 additions and 6 deletions.
  1. +3 −2 README.md
  2. +10 −0 ext/oj/fast.c
  3. +12 −1 ext/oj/load.c
  4. +1 −1 lib/oj/version.rb
  5. +14 −2 test/tests.rb
View
5 README.md
@@ -32,10 +32,11 @@ A fast JSON parser and Object marshaller as a Ruby gem.
## <a name="release">Release Notes</a>
-### Release 1.3.5
+### Release 1.3.6
- - Fixed mimic_JSON so it convinces Ruby that the **ALL** versions of the json gem are already loaded.
+ - Oj.load() now raises a SystemStackError if a JSON is too deeply nested. The loading is allowed to use on 75% of the stack.
+ - Oj::Doc.open() now raises a SystemStackError if a JSON is too deeply nested. The loading is allowed to use on 75% of the stack. Oj::Doc.open will allow much deeper nesting than Oj.load().
## <a name="description">Description</a>
View
10 ext/oj/fast.c
@@ -63,6 +63,7 @@ typedef struct _ParseInfo {
char *str; /* buffer being read from */
char *s; /* current position in buffer */
Doc doc;
+ uint64_t stack_min;
} *ParseInfo;
static void leaf_init(Leaf leaf, int type);
@@ -477,6 +478,9 @@ static Leaf
read_next(ParseInfo pi) {
Leaf leaf = 0;
+ if ((uint64_t)&leaf < pi->stack_min) {
+ rb_raise(rb_eSysStackError, "JSON is too deeply nested");
+ }
next_non_white(pi); // skip white space
switch (*pi->s) {
case '{':
@@ -827,6 +831,7 @@ parse_json(VALUE clas, char *json, int given, int allocated) {
VALUE result = Qnil;
Doc doc;
int ex = 0;
+ struct rlimit lim;
if (given) {
doc = ALLOCA_N(struct _Doc, 1);
@@ -837,6 +842,11 @@ parse_json(VALUE clas, char *json, int given, int allocated) {
pi.s = pi.str;
doc_init(doc);
pi.doc = doc;
+ if (0 == getrlimit(RLIMIT_STACK, &lim)) {
+ pi.stack_min = (uint64_t)&lim - (lim.rlim_cur / 4 * 3); // let 3/4ths of the stack be used only
+ } else {
+ pi.stack_min = 0; // indicates not to check stack limit
+ }
// last arg is free func void* func(void*)
doc->self = rb_data_object_alloc(clas, doc, 0, free_doc_cb);
rb_gc_register_address(&doc->self);
View
13 ext/oj/load.c
@@ -29,8 +29,9 @@
*/
#ifdef SAFE_CACHE
-#include <pthread.h> // TBD LOCK
+#include <pthread.h>
#endif
+#include <sys/resource.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@@ -59,6 +60,7 @@ typedef struct _ParseInfo {
char *s; /* current position in buffer */
CircArray circ_array;
Options options;
+ uint64_t stack_min;
} *ParseInfo;
static CircArray circ_array_new(void);
@@ -319,6 +321,9 @@ static VALUE
read_next(ParseInfo pi, int hint) {
VALUE obj;
+ if ((uint64_t)&obj < pi->stack_min) {
+ rb_raise(rb_eSysStackError, "JSON is too deeply nested");
+ }
next_non_white(pi); // skip white space
switch (*pi->s) {
case '{':
@@ -998,6 +1003,7 @@ VALUE
oj_parse(char *json, Options options) {
VALUE obj;
struct _ParseInfo pi;
+ struct rlimit lim;
if (0 == json) {
raise_error("Invalid arg, xml string can not be null", json, 0);
@@ -1010,6 +1016,11 @@ oj_parse(char *json, Options options) {
pi.circ_array = circ_array_new();
}
pi.options = options;
+ if (0 == getrlimit(RLIMIT_STACK, &lim)) {
+ pi.stack_min = (uint64_t)&lim - (lim.rlim_cur / 4 * 3); // let 3/4ths of the stack be used only
+ } else {
+ pi.stack_min = 0; // indicates not to check stack limit
+ }
obj = read_next(&pi, 0);
if (Yes == options->circular) {
circ_array_free(pi.circ_array);
View
2 lib/oj/version.rb
@@ -1,5 +1,5 @@
module Oj
# Current version of the module.
- VERSION = '1.3.5'
+ VERSION = '1.3.6'
end
View
16 test/tests.rb
@@ -275,7 +275,13 @@ def test_xml_time_compat
t = Time.local(2012, 1, 5, 23, 58, 7, 123456)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema)
tz = t.utc_offset
- assert_equal(%{"2012-01-05T23:58:07.123456000+%02d:%02d"} % [tz / 3600, tz / 60 % 60], json)
+ # Ruby does not handle a %+02d properly so...
+ sign = '+'
+ if 0 > tz
+ sign = '-'
+ tz = -tz
+ end
+ assert_equal(%{"2012-01-05T23:58:07.123456000%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json)
end
end
def test_xml_time_compat_no_secs
@@ -288,7 +294,13 @@ def test_xml_time_compat_no_secs
t = Time.local(2012, 1, 5, 23, 58, 7, 0)
json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema)
tz = t.utc_offset
- assert_equal(%{"2012-01-05T23:58:07+%02d:%02d"} % [tz / 3600, tz / 60 % 60], json)
+ # Ruby does not handle a %+02d properly so...
+ sign = '+'
+ if 0 > tz
+ sign = '-'
+ tz = -tz
+ end
+ assert_equal(%{"2012-01-05T23:58:07%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json)
end
end
def test_xml_time_compat_zulu

0 comments on commit 9d0c45a

Please sign in to comment.
Something went wrong with that request. Please try again.