From a61519f88a85fe5956e346d0c16b7b62b9d45e03 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Wed, 24 Jan 2024 17:59:28 +0100 Subject: [PATCH 01/12] Result percentage changed to a fraction --- sio/executors/checker.py | 31 ++++++++++++++++++++++++++++-- sio/workers/test/test_executors.py | 2 +- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/sio/executors/checker.py b/sio/executors/checker.py index cda6b2a..f4e9f2b 100644 --- a/sio/executors/checker.py +++ b/sio/executors/checker.py @@ -3,6 +3,7 @@ import logging import tempfile import six +import re from sio.workers import ft from sio.workers.executors import ( @@ -129,9 +130,35 @@ def run(environ, use_sandboxes=True): environ['result_code'] = 'OK' if output[1]: environ['result_string'] = _limit_length(output[1]) - environ['result_percentage'] = float(output[2] or 100) + environ['result_percentage'] = output_to_fraction(output[2]) else: environ['result_code'] = 'WA' environ['result_string'] = _limit_length(output[1]) - environ['result_percentage'] = 0 + environ['result_percentage'] = (0, 1) return environ + + +def output_to_fraction(output_str): + if not output_str: + return 100, 1 + output_is_float = float_pattern.match(r"[0-9]+\.[0-9]*", output_str) + output_is_percent = percent_pattern.match(r"[0-9]+", output_str) + output_is_fraction = fraction_pattern.match(r"[0-9]+\ [0-9]+", output_str) + if output_is_float: + return float_to_fraction(output_str) + elif output_is_percent: + return int(output_str), 1 + elif output_is_fraction: + return tuple(output_str.split(" ")) + else: + raise CheckerError( + 'Invalid checker output, expected float, percent or fraction, got "%s"' + % output_str + ) + + +def float_to_fraction(float_str): + nominator = int(''.join(filter(str.isdigit, float_str))) + denominator = 10 ** (len(float_str) - float_str.find('.') - 1) + return nominator, denominator + diff --git a/sio/workers/test/test_executors.py b/sio/workers/test/test_executors.py index 1a43de7..8a45ddb 100644 --- a/sio/workers/test/test_executors.py +++ b/sio/workers/test/test_executors.py @@ -350,7 +350,7 @@ def test_truncating_output(): def _make_untrusted_checkers_cases(): def ok_42(env): res_ok(env) - eq_(42, int(env['result_percentage'])) + eq_(42, int(env['result_percentage'][0] / env['result_percentage'][1])) # Test if unprotected execution allows for return code 1 yield '/chk-rtn1.c', None, False, None From 95946cfd6771f7c246e8d3e0fc54e8d5a6db43e4 Mon Sep 17 00:00:00 2001 From: Mateusz Masiarz Date: Tue, 20 Feb 2024 13:11:52 +0100 Subject: [PATCH 02/12] Fix typos --- sio/executors/checker.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sio/executors/checker.py b/sio/executors/checker.py index f4e9f2b..b2b9dd3 100644 --- a/sio/executors/checker.py +++ b/sio/executors/checker.py @@ -141,9 +141,9 @@ def run(environ, use_sandboxes=True): def output_to_fraction(output_str): if not output_str: return 100, 1 - output_is_float = float_pattern.match(r"[0-9]+\.[0-9]*", output_str) - output_is_percent = percent_pattern.match(r"[0-9]+", output_str) - output_is_fraction = fraction_pattern.match(r"[0-9]+\ [0-9]+", output_str) + output_is_float = re.match(r"[0-9]+\.[0-9]*", output_str) + output_is_percent = re.match(r"[0-9]+", output_str) + output_is_fraction = re.match(r"[0-9]+ [0-9]+", output_str) if output_is_float: return float_to_fraction(output_str) elif output_is_percent: @@ -161,4 +161,3 @@ def float_to_fraction(float_str): nominator = int(''.join(filter(str.isdigit, float_str))) denominator = 10 ** (len(float_str) - float_str.find('.') - 1) return nominator, denominator - From f5019880cf9cd1243b2d023004602b10f44db44c Mon Sep 17 00:00:00 2001 From: Mateusz Masiarz Date: Tue, 20 Feb 2024 13:12:40 +0100 Subject: [PATCH 03/12] Extend tests --- sio/workers/test/sources/chk-float.c | 16 ++++++++++++++++ sio/workers/test/sources/chk-percentage.c | 16 ++++++++++++++++ sio/workers/test/test_executors.py | 4 ++++ 3 files changed, 36 insertions(+) create mode 100644 sio/workers/test/sources/chk-float.c create mode 100644 sio/workers/test/sources/chk-percentage.c diff --git a/sio/workers/test/sources/chk-float.c b/sio/workers/test/sources/chk-float.c new file mode 100644 index 0000000..64f0a4b --- /dev/null +++ b/sio/workers/test/sources/chk-float.c @@ -0,0 +1,16 @@ +#include +/* Simple unsafe checker with buffer overflow */ + +int main(int argc, char **argv) { + char buf[255], buf2[255]; + FILE* fdi = fopen(argv[1], "r"); + FILE* fdo = fopen(argv[2], "r"); + FILE* fdh = fopen(argv[3], "r"); + fscanf(fdh, "%s", buf); + fscanf(fdo, "%s", buf2); + if (strcmp(buf, buf2) == 0) + puts("OK\nOK\n42.00"); + else + puts("WRONG"); + return 0; +} diff --git a/sio/workers/test/sources/chk-percentage.c b/sio/workers/test/sources/chk-percentage.c new file mode 100644 index 0000000..ad5e3ac --- /dev/null +++ b/sio/workers/test/sources/chk-percentage.c @@ -0,0 +1,16 @@ +#include +/* Simple unsafe checker with buffer overflow */ + +int main(int argc, char **argv) { + char buf[255], buf2[255]; + FILE* fdi = fopen(argv[1], "r"); + FILE* fdo = fopen(argv[2], "r"); + FILE* fdh = fopen(argv[3], "r"); + fscanf(fdh, "%s", buf); + fscanf(fdo, "%s", buf2); + if (strcmp(buf, buf2) == 0) + puts("OK\nOK\n84 2"); + else + puts("WRONG"); + return 0; +} diff --git a/sio/workers/test/test_executors.py b/sio/workers/test/test_executors.py index 8a45ddb..6fa3c78 100644 --- a/sio/workers/test/test_executors.py +++ b/sio/workers/test/test_executors.py @@ -363,6 +363,10 @@ def ok_42(env): yield '/open2.c', res_wa, True, None # Wrong model solution yield '/chk-rtn2.c', None, True, SystemError + # Checker with float result percentage + yield '/chk-float.c', ok_42, True, None + # Checker with fraction result percentage + yield '/chk-fraction.c', ok_42, True, None @pytest.mark.parametrize( From 679b65c02e1c51793ba5c6955194274ecdafaadc Mon Sep 17 00:00:00 2001 From: Mateusz Masiarz Date: Tue, 20 Feb 2024 13:49:36 +0100 Subject: [PATCH 04/12] More tests --- .../{chk-percentage.c => chk-fraction.c} | 0 sio/workers/test/test_executors.py | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) rename sio/workers/test/sources/{chk-percentage.c => chk-fraction.c} (100%) diff --git a/sio/workers/test/sources/chk-percentage.c b/sio/workers/test/sources/chk-fraction.c similarity index 100% rename from sio/workers/test/sources/chk-percentage.c rename to sio/workers/test/sources/chk-fraction.c diff --git a/sio/workers/test/test_executors.py b/sio/workers/test/test_executors.py index 6fa3c78..d1a406a 100644 --- a/sio/workers/test/test_executors.py +++ b/sio/workers/test/test_executors.py @@ -21,7 +21,11 @@ from sio.executors.common import run as run_executor from sio.executors.ingen import run as run_ingen from sio.executors.inwer import run as run_inwer -from sio.executors.checker import RESULT_STRING_LENGTH_LIMIT +from sio.executors.checker import ( + RESULT_STRING_LENGTH_LIMIT, + output_to_fraction, + CheckerError, +) from sio.workers import ft from sio.workers.execute import execute from sio.workers.executors import ( @@ -830,3 +834,16 @@ def test_execute(): eq_(rc, 0) rc, out = execute(['ls', tempcwd()]) in_(b'spam', out) + + +def test_checker_percentage_parsing(): + eq_(output_to_fraction('42'), (42, 1)) + eq_(output_to_fraction('42.123'), (42123, 1000)) + eq_(output_to_fraction('42 21'), (42, 21)) + + with pytest.raises(CheckerError): + output_to_fraction('42 2') + with pytest.raises(CheckerError): + output_to_fraction('42,2') + with pytest.raises(CheckerError): + output_to_fraction('42 2 1') From e1daa65003d22e1b2485d3cb67c0aa9ae6ddc22a Mon Sep 17 00:00:00 2001 From: Mateusz Masiarz Date: Tue, 20 Feb 2024 13:49:46 +0100 Subject: [PATCH 05/12] More error checking --- sio/executors/checker.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/sio/executors/checker.py b/sio/executors/checker.py index b2b9dd3..20a4ee4 100644 --- a/sio/executors/checker.py +++ b/sio/executors/checker.py @@ -130,7 +130,14 @@ def run(environ, use_sandboxes=True): environ['result_code'] = 'OK' if output[1]: environ['result_string'] = _limit_length(output[1]) - environ['result_percentage'] = output_to_fraction(output[2]) + if isinstance(output[2], bytes): + output[2] = output[2].decode('utf-8') + environ['result_percentage'] = output_to_fraction(output[2].strip()) + if environ['result_percentage'][1] == 0: + raise CheckerError( + 'Checker returned 0 as denominator. Checker stdout: "%s". Checker environ dump: %s' + % (output[2], environ) + ) else: environ['result_code'] = 'WA' environ['result_string'] = _limit_length(output[1]) @@ -141,15 +148,16 @@ def run(environ, use_sandboxes=True): def output_to_fraction(output_str): if not output_str: return 100, 1 - output_is_float = re.match(r"[0-9]+\.[0-9]*", output_str) - output_is_percent = re.match(r"[0-9]+", output_str) - output_is_fraction = re.match(r"[0-9]+ [0-9]+", output_str) + output_is_float = re.match(r"^[0-9]+\.[0-9]*$", output_str) + output_is_percent = re.match(r"^[0-9]+$", output_str) + output_is_fraction = re.match(r"^[0-9]+ [0-9]+$", output_str) if output_is_float: return float_to_fraction(output_str) elif output_is_percent: return int(output_str), 1 elif output_is_fraction: - return tuple(output_str.split(" ")) + split = output_str.split(' ') + return int(split[0]), int(split[1]) else: raise CheckerError( 'Invalid checker output, expected float, percent or fraction, got "%s"' From af39c5faabd938ffb45734649e7103d991652b29 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Thu, 14 Mar 2024 18:29:04 +0100 Subject: [PATCH 06/12] Simplified result percentage string parsing. --- sio/executors/checker.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/sio/executors/checker.py b/sio/executors/checker.py index f4e9f2b..daab189 100644 --- a/sio/executors/checker.py +++ b/sio/executors/checker.py @@ -141,24 +141,11 @@ def run(environ, use_sandboxes=True): def output_to_fraction(output_str): if not output_str: return 100, 1 - output_is_float = float_pattern.match(r"[0-9]+\.[0-9]*", output_str) - output_is_percent = percent_pattern.match(r"[0-9]+", output_str) - output_is_fraction = fraction_pattern.match(r"[0-9]+\ [0-9]+", output_str) - if output_is_float: - return float_to_fraction(output_str) - elif output_is_percent: - return int(output_str), 1 - elif output_is_fraction: - return tuple(output_str.split(" ")) - else: + try: + frac = Fraction(output_str) + return frac.numerator, frac.denominator + except ValueError: raise CheckerError( 'Invalid checker output, expected float, percent or fraction, got "%s"' % output_str - ) - - -def float_to_fraction(float_str): - nominator = int(''.join(filter(str.isdigit, float_str))) - denominator = 10 ** (len(float_str) - float_str.find('.') - 1) - return nominator, denominator - + ) \ No newline at end of file From 132a0f7ae96bb5c80420fca0e5017381ea618b0d Mon Sep 17 00:00:00 2001 From: Zonkil Date: Thu, 14 Mar 2024 18:38:47 +0100 Subject: [PATCH 07/12] Added division by zero error handling. --- sio/executors/checker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sio/executors/checker.py b/sio/executors/checker.py index daab189..59b54b2 100644 --- a/sio/executors/checker.py +++ b/sio/executors/checker.py @@ -148,4 +148,6 @@ def output_to_fraction(output_str): raise CheckerError( 'Invalid checker output, expected float, percent or fraction, got "%s"' % output_str - ) \ No newline at end of file + ) + except ZeroDivisionError: + raise CheckerError('Zero division in checker output "%s"' % output_str) \ No newline at end of file From 16419a98b8462eb7d9f8084b7d236f99e30eb142 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Thu, 14 Mar 2024 18:46:43 +0100 Subject: [PATCH 08/12] Added and changed tests. --- sio/workers/test/test_executors.py | 34 ++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/sio/workers/test/test_executors.py b/sio/workers/test/test_executors.py index d1a406a..22f678a 100644 --- a/sio/workers/test/test_executors.py +++ b/sio/workers/test/test_executors.py @@ -839,11 +839,33 @@ def test_execute(): def test_checker_percentage_parsing(): eq_(output_to_fraction('42'), (42, 1)) eq_(output_to_fraction('42.123'), (42123, 1000)) - eq_(output_to_fraction('42 21'), (42, 21)) - - with pytest.raises(CheckerError): - output_to_fraction('42 2') - with pytest.raises(CheckerError): + eq_(output_to_fraction('42/21'), (2, 1)) + eq_(output_to_fraction('42.'), (42, 1)) + eq_(output_to_fraction('007'), (7, 1)) + eq_(output_to_fraction('007/0042'), (1, 6)) + eq_(output_to_fraction('1e5'), (100000, 1)) + + with raises(CheckerError): + output_to_fraction('42 2') + with raises(CheckerError): output_to_fraction('42,2') - with pytest.raises(CheckerError): + with raises(CheckerError): output_to_fraction('42 2 1') + with raises(CheckerError): + output_to_fraction('42/2/1') + with raises(CheckerError): + output_to_fraction('42/2.1') + with raises(CheckerError): + output_to_fraction('') + with raises(CheckerError): + output_to_fraction('42/') + with raises(CheckerError): + output_to_fraction('/42') + with raises(CheckerError): + output_to_fraction('/') + with raises(CheckerError): + output_to_fraction('42/0') + with raises(CheckerError): + output_to_fraction('abc') + with raises(CheckerError): + output_to_fraction('42/abc') From aee2e30d8b235c3772d7678e5638cc0ce03645af Mon Sep 17 00:00:00 2001 From: Zonkil Date: Thu, 14 Mar 2024 18:55:13 +0100 Subject: [PATCH 09/12] fix: added fraction package --- sio/executors/checker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sio/executors/checker.py b/sio/executors/checker.py index 59b54b2..d2300c6 100644 --- a/sio/executors/checker.py +++ b/sio/executors/checker.py @@ -4,6 +4,7 @@ import tempfile import six import re +from fractions import Fraction from sio.workers import ft from sio.workers.executors import ( From dc2b1aa83e4eabb919c80045caf868b029e79ced Mon Sep 17 00:00:00 2001 From: Zonkil Date: Thu, 14 Mar 2024 19:05:41 +0100 Subject: [PATCH 10/12] fixed attribute error --- sio/workers/test/test_executors.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sio/workers/test/test_executors.py b/sio/workers/test/test_executors.py index 22f678a..c79c7d0 100644 --- a/sio/workers/test/test_executors.py +++ b/sio/workers/test/test_executors.py @@ -845,27 +845,27 @@ def test_checker_percentage_parsing(): eq_(output_to_fraction('007/0042'), (1, 6)) eq_(output_to_fraction('1e5'), (100000, 1)) - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('42 2') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('42,2') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('42 2 1') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('42/2/1') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('42/2.1') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('42/') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('/42') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('/') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('42/0') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('abc') - with raises(CheckerError): + with pytest.raises(CheckerError): output_to_fraction('42/abc') From 7b0fc72c78e81cd03b1a009ab65d3719d698ba39 Mon Sep 17 00:00:00 2001 From: Zonkil Date: Thu, 14 Mar 2024 19:35:50 +0100 Subject: [PATCH 11/12] fixed type error --- sio/executors/checker.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sio/executors/checker.py b/sio/executors/checker.py index d2300c6..98512ef 100644 --- a/sio/executors/checker.py +++ b/sio/executors/checker.py @@ -142,6 +142,8 @@ def run(environ, use_sandboxes=True): def output_to_fraction(output_str): if not output_str: return 100, 1 + if isinstance(output_str, bytes): + output_str = output_str.decode('utf-8') try: frac = Fraction(output_str) return frac.numerator, frac.denominator @@ -151,4 +153,6 @@ def output_to_fraction(output_str): % output_str ) except ZeroDivisionError: - raise CheckerError('Zero division in checker output "%s"' % output_str) \ No newline at end of file + raise CheckerError('Zero division in checker output "%s"' % output_str) + except TypeError: + raise CheckerError('Invalid checker output "%s"' % output_str) \ No newline at end of file From 14f6cc39ecb9f30b89665a3bd153dd5a44709a8b Mon Sep 17 00:00:00 2001 From: Zonkil Date: Thu, 14 Mar 2024 19:56:08 +0100 Subject: [PATCH 12/12] fixed tests --- sio/workers/test/sources/chk-fraction.c | 2 +- sio/workers/test/test_executors.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sio/workers/test/sources/chk-fraction.c b/sio/workers/test/sources/chk-fraction.c index ad5e3ac..f8bcf12 100644 --- a/sio/workers/test/sources/chk-fraction.c +++ b/sio/workers/test/sources/chk-fraction.c @@ -9,7 +9,7 @@ int main(int argc, char **argv) { fscanf(fdh, "%s", buf); fscanf(fdo, "%s", buf2); if (strcmp(buf, buf2) == 0) - puts("OK\nOK\n84 2"); + puts("OK\nOK\n84/2"); else puts("WRONG"); return 0; diff --git a/sio/workers/test/test_executors.py b/sio/workers/test/test_executors.py index c79c7d0..e0df4cd 100644 --- a/sio/workers/test/test_executors.py +++ b/sio/workers/test/test_executors.py @@ -844,6 +844,7 @@ def test_checker_percentage_parsing(): eq_(output_to_fraction('007'), (7, 1)) eq_(output_to_fraction('007/0042'), (1, 6)) eq_(output_to_fraction('1e5'), (100000, 1)) + eq_(output_to_fraction(''), (100, 1)) with pytest.raises(CheckerError): output_to_fraction('42 2') @@ -855,8 +856,7 @@ def test_checker_percentage_parsing(): output_to_fraction('42/2/1') with pytest.raises(CheckerError): output_to_fraction('42/2.1') - with pytest.raises(CheckerError): - output_to_fraction('') + with pytest.raises(CheckerError): output_to_fraction('42/') with pytest.raises(CheckerError):