From: Dominik Csapak <d.csapak@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH common 3/3] fix #7612: json schema: don't allow integers that are too large
Date: Thu, 8 Oct 2026 15:48:13 +0200 [thread overview]
Message-ID: <20261008135224.3323985-4-d.csapak@proxmox.com> (raw)
In-Reply-To: <20261008135224.3323985-1-d.csapak@proxmox.com>
very large integers (such as 1e400) are represented as 'inf' which
are serialized out as such by the JSON module. Since that is invalid
JSON, we want to catch these invalid values already during parsing,
because +/- infinity rarely make sense when we expect a number.
Improve the check by also converting the string to a number and back,
then check this for 'inf/nan' variants via Scalar::Util::Numeric.
This now let's some numeric test fail for huge numbers, while still
allowing smaller ones and correctly identifying non-numeric
strings.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
debian/control | 1 +
src/PVE/JSONSchema.pm | 16 +++++++--
test/json-schema-test.pl | 76 ++++++++++++++++++++++++++++------------
3 files changed, 69 insertions(+), 24 deletions(-)
diff --git a/debian/control b/debian/control
index ef9b0eb..3c78e55 100644
--- a/debian/control
+++ b/debian/control
@@ -48,6 +48,7 @@ Depends: libanyevent-perl,
libnetaddr-ip-perl,
libproxmox-acme-perl,
libproxmox-rs-perl,
+ libscalar-util-numeric-perl,
libstring-shellquote-perl,
libtimedate-perl,
liburi-perl,
diff --git a/src/PVE/JSONSchema.pm b/src/PVE/JSONSchema.pm
index db2c167..f06b282 100644
--- a/src/PVE/JSONSchema.pm
+++ b/src/PVE/JSONSchema.pm
@@ -11,6 +11,7 @@ use Getopt::Long;
use HTTP::Status qw(:constants);
use JSON;
use Net::IP qw(:PROC);
+use Scalar::Util::Numeric qw(isinf isnan);
use Storable; # for dclone
use PVE::Exception qw(raise);
@@ -1162,17 +1163,28 @@ sub add_error {
}
}
+# test values if they're in numeric finite range with the given REGEX
+my sub numeric_test {
+ my ($value, $NUM_RE) = @_;
+
+ my $res = $value =~ $NUM_RE;
+ return $res if !$res;
+ my $num = $value;
+ $num += 0;
+ return !isinf($num) && !isnan($num);
+}
+
sub is_number {
my $value = shift;
# see 'man perlretut'
- return $value =~ /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?\z/;
+ return numeric_test($value, qr/^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?\z/);
}
sub is_integer {
my $value = shift;
- return $value =~ m/^[+-]?\d+\z/;
+ return numeric_test($value, qr/^[+-]?\d+\z/);
}
sub check_type {
diff --git a/test/json-schema-test.pl b/test/json-schema-test.pl
index c5014e9..cda7c4d 100755
--- a/test/json-schema-test.pl
+++ b/test/json-schema-test.pl
@@ -112,26 +112,26 @@ my $property_string_tests = [
'num-ranged' => { type => 'number', optional => 1, minimum => -1, maximum => 1 },
},
subtests => [
- { in => "int=$huge_int", out => { int => $huge_int } },
- { in => "int=-$huge_int", out => { int => "-$huge_int" } },
- { in => "num=$huge_int", out => { num => $huge_int } },
- { in => "num=1e400", out => { num => '1e400' } },
- { in => "num=-1e400", out => { num => '-1e400' } },
+ { in => "int=$huge_int", must_fail => qr/type check \('integer'\) failed/ },
+ { in => "int=-$huge_int", must_fail => qr/type check \('integer'\) failed/ },
+ { in => "num=$huge_int", must_fail => qr/type check \('number'\) failed/ },
+ { in => "num=1e400", must_fail => qr/type check \('number'\) failed/ },
+ { in => "num=-1e400", must_fail => qr/type check \('number'\) failed/ },
{
in => "int-ranged=$huge_int",
- must_fail => qr/int-ranged: value must have a maximum value of 10/,
+ must_fail => qr/type check \('integer'\) failed/,
},
{
in => "int-ranged=-$huge_int",
- must_fail => qr/int-ranged: value must have a minimum value of -10/,
+ must_fail => qr/type check \('integer'\) failed/,
},
{
in => "num-ranged=1e400",
- must_fail => qr/num-ranged: value must have a maximum value of 1/,
+ must_fail => qr/type check \('number'\) failed/,
},
{
in => "num-ranged=-1e400",
- must_fail => qr/num-ranged: value must have a minimum value of -1/,
+ must_fail => qr/type check \('number'\) failed/,
},
],
},
@@ -933,9 +933,21 @@ my $numeric_tests = [
name => 'huge integer',
schema => { type => 'integer' },
subtests => [
- { name => 'positive', in => $huge_int },
- { name => 'negative', in => "-$huge_int" },
- { name => 'explicit plus sign', in => "+$huge_int" },
+ {
+ name => 'positive',
+ in => $huge_int,
+ must_fail => qr/^type check \('integer'\) failed/,
+ },
+ {
+ name => 'negative',
+ in => "-$huge_int",
+ must_fail => qr/^type check \('integer'\) failed/,
+ },
+ {
+ name => 'explicit plus sign',
+ in => "+$huge_int",
+ must_fail => qr/^type check \('integer'\) failed/,
+ },
{
name => 'exponent notation',
in => '1e400',
@@ -955,12 +967,12 @@ my $numeric_tests = [
{
name => 'above maximum',
in => $huge_int,
- must_fail => qr/^value must have a maximum value of 10$/,
+ must_fail => qr/^type check \('integer'\) failed/,
},
{
name => 'below minimum',
in => "-$huge_int",
- must_fail => qr/^value must have a minimum value of -10$/,
+ must_fail => qr/^type check \('integer'\) failed/,
},
],
},
@@ -968,11 +980,31 @@ my $numeric_tests = [
name => 'huge number',
schema => { type => 'number' },
subtests => [
- { name => 'integer notation', in => $huge_int },
- { name => 'negative integer notation', in => "-$huge_int" },
- { name => 'fractional part', in => "$huge_int.5" },
- { name => 'exponent notation', in => '1e400' },
- { name => 'negative exponent notation', in => '-1e400' },
+ {
+ name => 'integer notation',
+ in => $huge_int,
+ must_fail => qr/^type check \('number'\) failed/,
+ },
+ {
+ name => 'negative integer notation',
+ in => "-$huge_int",
+ must_fail => qr/^type check \('number'\) failed/,
+ },
+ {
+ name => 'fractional part',
+ in => "$huge_int.5",
+ must_fail => qr/^type check \('number'\) failed/,
+ },
+ {
+ name => 'exponent notation',
+ in => '1e400',
+ must_fail => qr/^type check \('number'\) failed/,
+ },
+ {
+ name => 'negative exponent notation',
+ in => '-1e400',
+ must_fail => qr/^type check \('number'\) failed/,
+ },
{ name => 'tiny exponent notation', in => '1e-400' },
{
name => 'infinity literal',
@@ -988,17 +1020,17 @@ my $numeric_tests = [
{
name => 'above maximum',
in => '1e400',
- must_fail => qr/^value must have a maximum value of 1$/,
+ must_fail => qr/^type check \('number'\) failed/,
},
{
name => 'above maximum in integer notation',
in => $huge_int,
- must_fail => qr/^value must have a maximum value of 1$/,
+ must_fail => qr/^type check \('number'\) failed/,
},
{
name => 'below minimum',
in => '-1e400',
- must_fail => qr/^value must have a minimum value of 0$/,
+ must_fail => qr/^type check \('number'\) failed/,
},
{ name => 'tiny value within range', in => '1e-400' },
],
--
2.47.3
prev parent reply other threads:[~2026-10-08 13:52 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-08 13:48 [PATCH common 0/3] fix #7612 Dominik Csapak
2026-10-08 13:48 ` [PATCH common 1/3] json schema: reorder imports to our style guide Dominik Csapak
2026-10-08 13:48 ` [PATCH common 2/3] json schema: add tests for numeric behavior Dominik Csapak
2026-10-08 13:48 ` Dominik Csapak [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261008135224.3323985-4-d.csapak@proxmox.com \
--to=d.csapak@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox