Skip to content

Commit 4ce839e

Browse files
authored
Merge pull request #761 from czj/fix/isdigit-unsigned-char
Avoid undefined behavior on non-ASCII bytes in the text date and timestamp decoders
2 parents 6c42b1a + 65c2dad commit 4ce839e

3 files changed

Lines changed: 30 additions & 19 deletions

File tree

‎ext/pg_text_decoder.c‎

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
#ifdef HAVE_INTTYPES_H
3535
#include <inttypes.h>
3636
#endif
37-
#include <ctype.h>
3837
#include <time.h>
3938
#if !defined(_WIN32)
4039
#include <arpa/inet.h>
@@ -614,7 +613,7 @@ static int parse_year(const char **str) {
614613
int i;
615614
const char * p = *str;
616615

617-
for(i = 0; isdigit(*p) && i < 7; i++, p++) {
616+
for(i = 0; rb_isdigit(*p) && i < 7; i++, p++) {
618617
year = 10 * year + char_to_digit(*p);
619618
}
620619

@@ -656,11 +655,11 @@ static VALUE pg_text_dec_timestamp(t_pg_coder *conv, const char *val, int len, i
656655
year = parse_year(&str);
657656

658657
if ( year > 0
659-
&& str[0] == '-' && isdigit(str[1]) && isdigit(str[2])
660-
&& str[3] == '-' && isdigit(str[4]) && isdigit(str[5])
661-
&& str[6] == ' ' && isdigit(str[7]) && isdigit(str[8])
662-
&& str[9] == ':' && isdigit(str[10]) && isdigit(str[11])
663-
&& str[12] == ':' && isdigit(str[13]) && isdigit(str[14])
658+
&& str[0] == '-' && rb_isdigit(str[1]) && rb_isdigit(str[2])
659+
&& str[3] == '-' && rb_isdigit(str[4]) && rb_isdigit(str[5])
660+
&& str[6] == ' ' && rb_isdigit(str[7]) && rb_isdigit(str[8])
661+
&& str[9] == ':' && rb_isdigit(str[10]) && rb_isdigit(str[11])
662+
&& str[12] == ':' && rb_isdigit(str[13]) && rb_isdigit(str[14])
664663
) {
665664

666665
mon = str2_to_int(str+1); str += 3;
@@ -669,7 +668,7 @@ static VALUE pg_text_dec_timestamp(t_pg_coder *conv, const char *val, int len, i
669668
min = str2_to_int(str+1); str += 3;
670669
sec = str2_to_int(str+1); str += 3;
671670

672-
if (str[0] == '.' && isdigit(str[1])) {
671+
if (str[0] == '.' && rb_isdigit(str[1])) {
673672
/* nano second part, up to 9 digits */
674673
static const int coef[9] = {
675674
100000000, 10000000, 1000000,
@@ -678,23 +677,23 @@ static VALUE pg_text_dec_timestamp(t_pg_coder *conv, const char *val, int len, i
678677
int i;
679678

680679
str++;
681-
for (i = 0; i < 9 && isdigit(*str); i++)
680+
for (i = 0; i < 9 && rb_isdigit(*str); i++)
682681
{
683682
nsec += coef[i] * char_to_digit(*str++);
684683
}
685684
/* consume digits smaller than nsec */
686-
while(isdigit(*str)) str++;
685+
while(rb_isdigit(*str)) str++;
687686
}
688687

689-
if ((str[0] == '+' || str[0] == '-') && isdigit(str[1]) && isdigit(str[2])) {
688+
if ((str[0] == '+' || str[0] == '-') && rb_isdigit(str[1]) && rb_isdigit(str[2])) {
690689
tz_given = str[0] == '-' ? TZ_NEG : TZ_POS;
691690
tz_hour = str2_to_int(str+1); str += 3;
692691

693-
if (str[0] == ':' && isdigit(str[1]) && isdigit(str[2]))
692+
if (str[0] == ':' && rb_isdigit(str[1]) && rb_isdigit(str[2]))
694693
{
695694
tz_min = str2_to_int(str+1); str += 3;
696695
}
697-
if (str[0] == ':' && isdigit(str[1]) && isdigit(str[2]))
696+
if (str[0] == ':' && rb_isdigit(str[1]) && rb_isdigit(str[2]))
698697
{
699698
tz_sec = str2_to_int(str+1); str += 3;
700699
}
@@ -954,9 +953,9 @@ static VALUE
954953
pg_text_dec_date(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
955954
{
956955
if (len == 10
957-
&& isdigit(val[0]) && isdigit(val[1]) && isdigit(val[2]) && isdigit(val[3])
958-
&& val[4] == '-' && isdigit(val[5]) && isdigit(val[6])
959-
&& val[7] == '-' && isdigit(val[8]) && isdigit(val[9])
956+
&& rb_isdigit(val[0]) && rb_isdigit(val[1]) && rb_isdigit(val[2]) && rb_isdigit(val[3])
957+
&& val[4] == '-' && rb_isdigit(val[5]) && rb_isdigit(val[6])
958+
&& val[7] == '-' && rb_isdigit(val[8]) && rb_isdigit(val[9])
960959
) {
961960
int year = str2_to_int(val) * 100 + str2_to_int(val + 2);
962961
int mon = str2_to_int(val + 5);

‎spec/pg/type_spec.rb‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,10 @@ def textdec_timestamptz_decode_should_fail(str)
219219
end
220220
it 'fails when the timestamp contains values with invalid characters' do
221221
str = '2013-01-02 23:23:59.123456+00:25:21'
222-
str.length.times do |i|
223-
textdec_timestamptz_decode_should_fail(str[0,i] + "x" + str[i+1..-1])
222+
["x", "²", "\xB2".b].each do |invalid|
223+
str.length.times do |i|
224+
textdec_timestamptz_decode_should_fail(str[0,i] + invalid + str[i+1..-1])
225+
end
224226
end
225227
end
226228
it 'fails when the timestamp contains leading characters' do
@@ -252,7 +254,7 @@ def textdec_timestamptz_decode_should_fail(str)
252254
expect( textdec_date.decode('10000-01-01') ).to eq( '10000-01-01' )
253255
end
254256
it 'returns dates in other formats as string' do
255-
%w[01/02/2016 02.01.2016 2016-1-02 2016-01-2 2016/01/02 2016-01ª0].each do |value|
257+
%w[01/02/2016 02.01.2016 2016-1-02 2016-01-2 2016/01/02 2016-01ª0 ²16-01-02 2016-²-02 2016-01-²].push("2016-01-0\xB2".b).each do |value|
256258
expect( textdec_date.decode(value) ).to eq( value )
257259
end
258260
expect( textdec_date.decode(' 2016-01-02') ).to eq( ' 2016-01-02' )

‎spec/pg_spec.rb‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@
7878
expect(cext_text).not_to match(/home\//) # there should be no path to /home/ or so
7979
end
8080

81+
it "C-ext sources don't call ctype.h functions, which are undefined for bytes >= 0x80 in a plain char" do
82+
calls = Dir[File.expand_path("../ext/*.[ch]", __dir__)].flat_map do |fname|
83+
File.binread(fname).lines.each_with_index.filter_map do |line, index|
84+
next if line =~ %r{\A\s*(/?\*|//)}
85+
"#{File.basename(fname)}:#{index + 1}" if line =~ /(?<!\w)(is(alnum|alpha|blank|cntrl|digit|graph|lower|print|punct|space|upper|xdigit)|to(lower|upper))\s*\(/
86+
end
87+
end
88+
expect(calls).to be_empty, "use the rb_is*() functions of ruby.h instead: #{calls.join(", ")}"
89+
end
90+
8191
it "native gem's libpq file shouldn't contain any rpath or other build-related paths" do
8292
skip "applies to native binary gems only" unless PG::IS_BINARY_GEM
8393

0 commit comments

Comments
 (0)