test_fountain_parsers.c (11429B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free 7 Software Foundation; either version 2.1, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for 12 more details. You should have received a copy along with TALER; see 13 COPYING.LGPL. If not, see <http://www.gnu.org/licenses/>. 14 */ 15 /** 16 * @file lib/test_fountain_parsers.c 17 * @brief Exercise the HTTP-200 parsers directly, including hostile array sizes. 18 */ 19 #define handle_ok info_handle_ok 20 #include "merchant_api_get-fountain-info.c" 21 #undef handle_ok 22 #define handle_ok withdraw_handle_ok 23 #include "merchant_api_post-fountain-withdraw.c" 24 #undef handle_ok 25 #define handle_ok list_handle_ok 26 #include "merchant_api_get-private-fountains.c" 27 #undef handle_ok 28 #define handle_ok detail_handle_ok 29 #include "merchant_api_get-private-fountains-FOUNTAIN_ID.c" 30 #undef handle_ok 31 #include <sys/resource.h> 32 33 struct Expected 34 { 35 unsigned int count; 36 unsigned int calls; 37 bool malformed; 38 }; 39 40 static void 41 info_cb (void *cls, 42 const struct TALER_MERCHANT_GetFountainInfoResponse *r) 43 { 44 struct Expected *e = cls; 45 46 e->calls++; 47 GNUNET_assert (e->malformed ? (0 == r->hr.http_status && 48 TALER_EC_GENERIC_REPLY_MALFORMED == r->hr.ec) 49 : (MHD_HTTP_OK == r->hr.http_status && 50 e->count == r->details.ok.grants_len)); 51 if ( (! e->malformed) && (0 != e->count) ) 52 GNUNET_assert (0 == strcmp (r->details.ok.grants[0].token_family.name, 53 "Test")); 54 } 55 56 57 static void 58 withdraw_cb (void *cls, 59 const struct TALER_MERCHANT_PostFountainWithdrawResponse *r) 60 { 61 struct Expected *e = cls; 62 63 e->calls++; 64 GNUNET_assert (e->malformed ? (0 == r->hr.http_status && 65 TALER_EC_GENERIC_REPLY_MALFORMED == r->hr.ec) 66 : (MHD_HTTP_OK == r->hr.http_status && 67 e->count == r->details.ok.results_len)); 68 if ( (! e->malformed) && (0 != e->count) ) 69 GNUNET_assert (0 == strcmp (r->details.ok.results[0].token_family_slug, 70 "test")); 71 } 72 73 74 static void 75 list_cb (void *cls, 76 const struct TALER_MERCHANT_GetPrivateFountainsResponse *r) 77 { 78 struct Expected *e = cls; 79 80 e->calls++; 81 GNUNET_assert (e->malformed ? (0 == r->hr.http_status && 82 TALER_EC_GENERIC_REPLY_MALFORMED == r->hr.ec) 83 : (MHD_HTTP_OK == r->hr.http_status && 84 e->count == r->details.ok.fountains_len)); 85 if ( (! e->malformed) && (0 != e->count) ) 86 GNUNET_assert (0 == strcmp (r->details.ok.fountains[0].fountain_id, 87 "test")); 88 } 89 90 91 static void 92 detail_cb (void *cls, 93 const struct TALER_MERCHANT_GetPrivateFountainResponse *r) 94 { 95 struct Expected *e = cls; 96 97 e->calls++; 98 GNUNET_assert (e->malformed ? (0 == r->hr.http_status && 99 TALER_EC_GENERIC_REPLY_MALFORMED == r->hr.ec) 100 : (MHD_HTTP_OK == r->hr.http_status && 101 e->count == r->details.ok.grants_len)); 102 if ( (! e->malformed) && (0 != e->count) ) 103 GNUNET_assert (0 == strcmp (r->details.ok.grants[0].token_family_slug, 104 "test")); 105 } 106 107 108 static void 109 check (json_t *grants, 110 unsigned int mode, 111 bool malformed) 112 { 113 struct Expected expected = { 114 .count = json_array_size (grants), 115 .malformed = malformed 116 }; 117 json_t *reply = json_pack ("{s:{s:i},s:O,s:O,s:s}", 118 "poll_freq", "d_us", 1000000, 119 "grants", grants, "fountains", grants, 120 "description", "Test"); 121 122 if (1 == mode) 123 { 124 struct TALER_MERCHANT_PostFountainWithdrawHandle h = { 125 .cb = &withdraw_cb, .cb_cls = &expected 126 }; 127 struct TALER_MERCHANT_PostFountainWithdrawResponse r = { 128 .hr = {.http_status = MHD_HTTP_OK, .reply = reply} 129 }; 130 131 withdraw_handle_ok (&h, &r); 132 } 133 else if (0 == mode) 134 { 135 struct TALER_MERCHANT_GetFountainInfoHandle h = { 136 .cb = &info_cb, .cb_cls = &expected 137 }; 138 struct TALER_MERCHANT_GetFountainInfoResponse r = { 139 .hr = {.http_status = MHD_HTTP_OK, .reply = reply} 140 }; 141 142 info_handle_ok (&h, &r); 143 } 144 else if (2 == mode) 145 { 146 struct TALER_MERCHANT_GetPrivateFountainsHandle h = { 147 .cb = &list_cb, .cb_cls = &expected 148 }; 149 struct TALER_MERCHANT_GetPrivateFountainsResponse r = { 150 .hr = {.http_status = MHD_HTTP_OK, .reply = reply} 151 }; 152 153 list_handle_ok (&h, &r); 154 } 155 else 156 { 157 struct TALER_MERCHANT_GetPrivateFountainHandle h = { 158 .cb = &detail_cb, .cb_cls = &expected 159 }; 160 struct TALER_MERCHANT_GetPrivateFountainResponse r = { 161 .hr = {.http_status = MHD_HTTP_OK, .reply = reply} 162 }; 163 164 detail_handle_ok (&h, &r); 165 } 166 GNUNET_assert (1 == expected.calls); 167 json_decref (reply); 168 } 169 170 171 /** 172 * Exercise each nested array through the real fountain-info parser. Use a 173 * preceding valid grant as well, so every failure tests partial cleanup. 174 */ 175 static void 176 check_nested_arrays (json_t *info) 177 { 178 const char *fields[] = { "keys", "expected_domains", "trusted_domains" }; 179 const unsigned int limits[] = { 180 TALER_MERCHANT_MAX_TOKEN_FAMILY_KEYS, 181 TALER_MERCHANT_MAX_TOKEN_FAMILY_DOMAINS, 182 TALER_MERCHANT_MAX_TOKEN_FAMILY_DOMAINS 183 }; 184 /* 10 MB of null keys, or 30 MB of null domains, fits the HTTP limit but 185 previously triggered a 48 MB allocation on a 64-bit client. */ 186 const unsigned int hostile_counts[] = { 2000000, 6000000, 6000000 }; 187 struct TALER_TokenIssuePrivateKey priv; 188 struct TALER_TokenIssuePublicKey pub; 189 struct GNUNET_TIME_Timestamp start = { 190 .abs_time.abs_value_us = 1000000 191 }; 192 struct GNUNET_TIME_Timestamp end = { 193 .abs_time.abs_value_us = 2000000 194 }; 195 json_t *key; 196 json_t *domain = json_string ("merchant.example"); 197 198 GNUNET_CRYPTO_blind_sign_keys_create (&priv.private_key, 199 &pub.public_key, 200 GNUNET_CRYPTO_BSA_CS); 201 key = GNUNET_JSON_PACK ( 202 TALER_JSON_pack_token_pub (NULL, &pub), 203 GNUNET_JSON_pack_timestamp ("signature_validity_start", start), 204 GNUNET_JSON_pack_timestamp ("signature_validity_end", end)); 205 GNUNET_CRYPTO_blind_sign_priv_decref (priv.private_key); 206 GNUNET_CRYPTO_blind_sign_pub_decref (pub.public_key); 207 for (unsigned int mode = 0; mode < 3; mode++) 208 { 209 json_t *grant = json_deep_copy (info); 210 json_t *family = json_object_get (grant, "token_family"); 211 json_t *details = json_object_get (family, "details"); 212 json_t *array = json_array (); 213 json_t *grants = json_array (); 214 json_t *valid = (0 == mode) ? key : domain; 215 216 if (2 == mode) 217 { 218 GNUNET_assert (0 == json_object_set_new ( 219 details, "class", json_string ("subscription"))); 220 GNUNET_assert (0 == json_object_del (details, "expected_domains")); 221 } 222 GNUNET_assert (0 == json_object_set ((0 == mode) ? family : details, 223 fields[mode], array)); 224 GNUNET_assert (0 == json_array_append (grants, info)); 225 GNUNET_assert (0 == json_array_append (grants, grant)); 226 check (grants, 0, false); 227 GNUNET_assert (0 == json_array_append (array, valid)); 228 check (grants, 0, false); 229 GNUNET_assert (0 == json_array_append_new (array, json_null ())); 230 check (grants, 0, true); 231 json_array_clear (array); 232 for (unsigned int i = 0; i < limits[mode]; i++) 233 GNUNET_assert (0 == json_array_append (array, valid)); 234 check (grants, 0, false); 235 GNUNET_assert (0 == json_array_append (array, valid)); 236 check (grants, 0, true); 237 json_array_clear (array); 238 for (unsigned int i = 0; i < hostile_counts[mode]; i++) 239 GNUNET_assert (0 == json_array_append_new (array, json_null ())); 240 check (grants, 0, true); 241 json_decref (grants); 242 json_decref (grant); 243 json_decref (array); 244 } 245 json_decref (key); 246 json_decref (domain); 247 } 248 249 250 int 251 main (void) 252 { 253 struct rlimit limit; 254 json_t *grants = json_array (); 255 json_t *info = json_loads ( 256 "{\"token_family_slug\":\"test\",\"tokens_per_period_limit\":1," 257 "\"tokens_per_period_stash\":1,\"key_window_size\":0," 258 "\"token_family\":{\"name\":\"Test\",\"description\":\"Test\"," 259 "\"keys\":[],\"critical\":false," 260 "\"details\":{\"class\":\"discount\",\"expected_domains\":[]}}}", 261 0, NULL); 262 struct TALER_TokenIssuePublicKeyHashP hash = {0}; 263 json_t *withdraw = GNUNET_JSON_PACK ( 264 GNUNET_JSON_pack_string ("token_family_slug", "test"), 265 GNUNET_JSON_pack_data_auto ("h_issue", &hash), 266 GNUNET_JSON_pack_array_steal ("token_sigs", json_array ())); 267 268 json_t *list = json_pack ("{s:s,s:s}", 269 "fountain_id", "test", "description", "Test"); 270 json_t *valid_grants[] = { info, withdraw, list, info }; 271 unsigned int large_counts[] = { 100000, 100000, 600000, 300000 }; 272 273 GNUNET_assert (0 == getrlimit (RLIMIT_STACK, &limit)); 274 limit.rlim_cur = GNUNET_MIN (limit.rlim_cur, 8 * 1024 * 1024); 275 GNUNET_assert (0 == setrlimit (RLIMIT_STACK, &limit)); 276 GNUNET_assert (NULL != info); 277 for (unsigned int mode = 0; mode < 4; mode++) 278 { 279 json_t *valid = valid_grants[mode]; 280 281 json_array_clear (grants); 282 check (grants, mode, false); 283 GNUNET_assert (0 == json_array_append (grants, valid)); 284 check (grants, mode, false); 285 /* Failure after a parsed grant must release partially parsed data. */ 286 GNUNET_assert (0 == json_array_append_new ( 287 grants, 288 json_pack ("{s:[]}", "token_sigs"))); 289 check (grants, mode, true); 290 json_array_clear (grants); 291 for (unsigned int i = 0; 292 i < TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS; i++) 293 GNUNET_assert (0 == json_array_append (grants, valid)); 294 check (grants, mode, false); 295 GNUNET_assert (0 == json_array_append (grants, valid)); 296 check (grants, mode, 2 != mode); 297 json_array_clear (grants); 298 for (unsigned int i = 0; i < large_counts[mode]; i++) 299 GNUNET_assert (0 == json_array_append_new (grants, json_null ())); 300 check (grants, mode, true); 301 } 302 /* A list is not capped at the grant limit, but still has a byte budget. */ 303 json_array_clear (grants); 304 for (size_t i = 0; 305 i < GNUNET_MAX_MALLOC_CHECKED / sizeof (struct TALER_MERCHANT_FountainEntry); 306 i++) 307 GNUNET_assert (0 == json_array_append (grants, list)); 308 check (grants, 2, true); 309 /* A small grant array must not hide an excessive signature total. */ 310 json_array_clear (grants); 311 for (unsigned int i = 0; i < 2; i++) 312 { 313 json_t *grant = json_deep_copy (withdraw); 314 json_t *sigs = json_object_get (grant, "token_sigs"); 315 316 for (unsigned int j = 0; 317 j <= TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS / 2; j++) 318 GNUNET_assert (0 == json_array_append_new (sigs, json_object ())); 319 GNUNET_assert (0 == json_array_append_new (grants, grant)); 320 } 321 check (grants, true, true); 322 json_decref (grants); 323 check_nested_arrays (info); 324 json_decref (info); 325 json_decref (withdraw); 326 json_decref (list); 327 return 0; 328 }