Skip to content

Commit 89f3991

Browse files
authored
Fix base64 validation and add unittests (#10515)
Implement proper padding character checks
1 parent 1bc753f commit 89f3991

File tree

4 files changed

+124
-6
lines changed

4 files changed

+124
-6
lines changed

doc/client_lua_api.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,9 @@ Call these functions only at load time!
910910
* Example: `minetest.rgba(10, 20, 30, 40)`, returns `"#0A141E28"`
911911
* `minetest.encode_base64(string)`: returns string encoded in base64
912912
* Encodes a string in base64.
913-
* `minetest.decode_base64(string)`: returns string
913+
* `minetest.decode_base64(string)`: returns string or nil on failure
914+
* Padding characters are only supported starting at version 5.4.0, where
915+
5.5.0 and newer perform proper checks.
914916
* Decodes a string encoded in base64.
915917
* `minetest.gettext(string)` : returns string
916918
* look up the translation of a string in the gettext message catalog

doc/lua_api.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5773,7 +5773,9 @@ Misc.
57735773
* Example: `minetest.rgba(10, 20, 30, 40)`, returns `"#0A141E28"`
57745774
* `minetest.encode_base64(string)`: returns string encoded in base64
57755775
* Encodes a string in base64.
5776-
* `minetest.decode_base64(string)`: returns string or nil for invalid base64
5776+
* `minetest.decode_base64(string)`: returns string or nil on failure
5777+
* Padding characters are only supported starting at version 5.4.0, where
5778+
5.5.0 and newer perform proper checks.
57775779
* Decodes a string encoded in base64.
57785780
* `minetest.is_protected(pos, name)`: returns boolean
57795781
* Returning `true` restricts the player `name` from modifying (i.e. digging,

src/unittest/test_utilities.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
2323
#include "util/enriched_string.h"
2424
#include "util/numeric.h"
2525
#include "util/string.h"
26+
#include "util/base64.h"
2627

2728
class TestUtilities : public TestBase {
2829
public:
@@ -56,6 +57,7 @@ class TestUtilities : public TestBase {
5657
void testMyround();
5758
void testStringJoin();
5859
void testEulerConversion();
60+
void testBase64();
5961
};
6062

6163
static TestUtilities g_test_instance;
@@ -87,6 +89,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
8789
TEST(testMyround);
8890
TEST(testStringJoin);
8991
TEST(testEulerConversion);
92+
TEST(testBase64);
9093
}
9194

9295
////////////////////////////////////////////////////////////////////////////////
@@ -537,3 +540,93 @@ void TestUtilities::testEulerConversion()
537540
setPitchYawRoll(m2, v2);
538541
UASSERT(within(m1, m2, tolL));
539542
}
543+
544+
void TestUtilities::testBase64()
545+
{
546+
// Test character set
547+
UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
548+
"abcdefghijklmnopqrstuvwxyz"
549+
"0123456789+/") == true);
550+
UASSERT(base64_is_valid("/+9876543210"
551+
"zyxwvutsrqponmlkjihgfedcba"
552+
"ZYXWVUTSRQPONMLKJIHGFEDCBA") == true);
553+
UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
554+
"abcdefghijklmnopqrstuvwxyz"
555+
"0123456789+.") == false);
556+
UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
557+
"abcdefghijklmnopqrstuvwxyz"
558+
"0123456789 /") == false);
559+
560+
// Test empty string
561+
UASSERT(base64_is_valid("") == true);
562+
563+
// Test different lengths, with and without padding,
564+
// with correct and incorrect padding
565+
UASSERT(base64_is_valid("A") == false);
566+
UASSERT(base64_is_valid("AA") == true);
567+
UASSERT(base64_is_valid("AAA") == true);
568+
UASSERT(base64_is_valid("AAAA") == true);
569+
UASSERT(base64_is_valid("AAAAA") == false);
570+
UASSERT(base64_is_valid("AAAAAA") == true);
571+
UASSERT(base64_is_valid("AAAAAAA") == true);
572+
UASSERT(base64_is_valid("AAAAAAAA") == true);
573+
UASSERT(base64_is_valid("A===") == false);
574+
UASSERT(base64_is_valid("AA==") == true);
575+
UASSERT(base64_is_valid("AAA=") == true);
576+
UASSERT(base64_is_valid("AAAA") == true);
577+
UASSERT(base64_is_valid("AAAA====") == false);
578+
UASSERT(base64_is_valid("AAAAA===") == false);
579+
UASSERT(base64_is_valid("AAAAAA==") == true);
580+
UASSERT(base64_is_valid("AAAAAAA=") == true);
581+
UASSERT(base64_is_valid("AAAAAAA==") == false);
582+
UASSERT(base64_is_valid("AAAAAAA===") == false);
583+
UASSERT(base64_is_valid("AAAAAAA====") == false);
584+
UASSERT(base64_is_valid("AAAAAAAA") == true);
585+
UASSERT(base64_is_valid("AAAAAAAA=") == false);
586+
UASSERT(base64_is_valid("AAAAAAAA==") == false);
587+
UASSERT(base64_is_valid("AAAAAAAA===") == false);
588+
UASSERT(base64_is_valid("AAAAAAAA====") == false);
589+
590+
// Test if canonical encoding
591+
// Last character limitations, length % 4 == 3
592+
UASSERT(base64_is_valid("AAB") == false);
593+
UASSERT(base64_is_valid("AAE") == true);
594+
UASSERT(base64_is_valid("AAQ") == true);
595+
UASSERT(base64_is_valid("AAB=") == false);
596+
UASSERT(base64_is_valid("AAE=") == true);
597+
UASSERT(base64_is_valid("AAQ=") == true);
598+
UASSERT(base64_is_valid("AAAAAAB=") == false);
599+
UASSERT(base64_is_valid("AAAAAAE=") == true);
600+
UASSERT(base64_is_valid("AAAAAAQ=") == true);
601+
// Last character limitations, length % 4 == 2
602+
UASSERT(base64_is_valid("AB") == false);
603+
UASSERT(base64_is_valid("AE") == false);
604+
UASSERT(base64_is_valid("AQ") == true);
605+
UASSERT(base64_is_valid("AB==") == false);
606+
UASSERT(base64_is_valid("AE==") == false);
607+
UASSERT(base64_is_valid("AQ==") == true);
608+
UASSERT(base64_is_valid("AAAAAB==") == false);
609+
UASSERT(base64_is_valid("AAAAAE==") == false);
610+
UASSERT(base64_is_valid("AAAAAQ==") == true);
611+
612+
// Extraneous character present
613+
UASSERT(base64_is_valid(".") == false);
614+
UASSERT(base64_is_valid("A.") == false);
615+
UASSERT(base64_is_valid("AA.") == false);
616+
UASSERT(base64_is_valid("AAA.") == false);
617+
UASSERT(base64_is_valid("AAAA.") == false);
618+
UASSERT(base64_is_valid("AAAAA.") == false);
619+
UASSERT(base64_is_valid("A.A") == false);
620+
UASSERT(base64_is_valid("AA.A") == false);
621+
UASSERT(base64_is_valid("AAA.A") == false);
622+
UASSERT(base64_is_valid("AAAA.A") == false);
623+
UASSERT(base64_is_valid("AAAAA.A") == false);
624+
UASSERT(base64_is_valid("\xE1""AAA") == false);
625+
626+
// Padding in wrong position
627+
UASSERT(base64_is_valid("A=A") == false);
628+
UASSERT(base64_is_valid("AA=A") == false);
629+
UASSERT(base64_is_valid("AAA=A") == false);
630+
UASSERT(base64_is_valid("AAAA=A") == false);
631+
UASSERT(base64_is_valid("AAAAA=A") == false);
632+
}

src/util/base64.cpp

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,39 @@ static const std::string base64_chars =
3333
"abcdefghijklmnopqrstuvwxyz"
3434
"0123456789+/";
3535

36+
static const std::string base64_chars_padding_1 = "AEIMQUYcgkosw048";
37+
static const std::string base64_chars_padding_2 = "AQgw";
3638

3739
static inline bool is_base64(unsigned char c)
3840
{
39-
return isalnum(c) || c == '+' || c == '/' || c == '=';
41+
return (c >= '0' && c <= '9')
42+
|| (c >= 'A' && c <= 'Z')
43+
|| (c >= 'a' && c <= 'z')
44+
|| c == '+' || c == '/';
4045
}
4146

4247
bool base64_is_valid(std::string const& s)
4348
{
44-
for (char i : s)
45-
if (!is_base64(i))
49+
size_t i = 0;
50+
for (; i < s.size(); ++i)
51+
if (!is_base64(s[i]))
52+
break;
53+
unsigned char padding = 3 - ((i + 3) % 4);
54+
if ((padding == 1 && base64_chars_padding_1.find(s[i - 1]) == std::string::npos)
55+
|| (padding == 2 && base64_chars_padding_2.find(s[i - 1]) == std::string::npos)
56+
|| padding == 3)
57+
return false;
58+
int actual_padding = s.size() - i;
59+
// omission of padding characters is allowed
60+
if (actual_padding == 0)
61+
return true;
62+
63+
// remaining characters (max. 2) may only be padding
64+
for (; i < s.size(); ++i)
65+
if (s[i] != '=')
4666
return false;
47-
return true;
67+
// number of padding characters needs to match
68+
return padding == actual_padding;
4869
}
4970

5071
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {

0 commit comments

Comments
 (0)