From 2e5019e439b1c79f4adcf8a0662df13d92bc9716 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Thu, 1 Oct 2026 16:45:59 -0600 Subject: [PATCH 1/2] return RC_INVALID_VALUE if expecting array of objects and got array of primitives --- src/rapi/rc_api_editor.c | 5 +++ src/rapi/rc_api_info.c | 20 ++++++++++ src/rapi/rc_api_runtime.c | 38 +++++++++++++++---- src/rapi/rc_api_user.c | 15 ++++++++ test/rapi/test_rc_api_user.c | 72 ++++++++++++++++++++++++------------ 5 files changed, 119 insertions(+), 31 deletions(-) diff --git a/src/rapi/rc_api_editor.c b/src/rapi/rc_api_editor.c index d2aab6fb..0e096aee 100644 --- a/src/rapi/rc_api_editor.c +++ b/src/rapi/rc_api_editor.c @@ -124,6 +124,11 @@ int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_resp ++note; } + + if (note < response->notes + response->num_notes) { + response->num_notes = (uint32_t)(note - response->notes); + return RC_INVALID_VALUE; + } } return RC_OK; diff --git a/src/rapi/rc_api_info.c b/src/rapi/rc_api_info.c index 327a6dff..23cd97a4 100644 --- a/src/rapi/rc_api_info.c +++ b/src/rapi/rc_api_info.c @@ -128,6 +128,11 @@ int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achieveme ++entry; } + + if (entry < response->recently_awarded + response->num_recently_awarded) { + response->num_recently_awarded = (uint32_t)(entry - response->recently_awarded); + return RC_INVALID_VALUE; + } } return RC_OK; @@ -295,6 +300,11 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa ++entry; } + + if (entry < response->entries + response->num_entries) { + response->num_entries = (uint32_t)(entry - response->entries); + return RC_INVALID_VALUE; + } } return RC_OK; @@ -415,6 +425,11 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp ++entry; } + + if (entry < response->entries + response->num_entries) { + response->num_entries = (uint32_t)(entry - response->entries); + return RC_INVALID_VALUE; + } } return RC_OK; @@ -514,6 +529,11 @@ int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_re ++entry; } + + if (entry < response->entries + response->num_entries) { + response->num_entries = (uint32_t)(entry - response->entries); + return RC_INVALID_VALUE; + } } return RC_OK; diff --git a/src/rapi/rc_api_runtime.c b/src/rapi/rc_api_runtime.c index 8bc3eac2..649ef7f2 100644 --- a/src/rapi/rc_api_runtime.c +++ b/src/rapi/rc_api_runtime.c @@ -108,7 +108,8 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r return rc_api_process_fetch_game_data_server_response(response, &response_obj); } -static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* response, rc_api_achievement_definition_t* achievement, rc_json_field_t* array_field) { +static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* response, rc_api_achievement_definition_t* achievement, uint32_t* num_achievements, rc_json_field_t* array_field) { + const rc_api_achievement_definition_t* first_achievement = achievement; rc_json_iterator_t iterator; const char* last_author = ""; const char* last_author_field = ""; @@ -220,10 +221,16 @@ static int rc_api_process_fetch_game_data_achievements(rc_api_response_t* respon ++achievement; } + if (achievement < first_achievement + *num_achievements) { + *num_achievements = (uint32_t)(achievement - first_achievement); + return RC_INVALID_VALUE; + } + return RC_OK; } -static int rc_api_process_fetch_game_data_leaderboards(rc_api_response_t* response, rc_api_leaderboard_definition_t* leaderboard, rc_json_field_t* array_field) { +static int rc_api_process_fetch_game_data_leaderboards(rc_api_response_t* response, rc_api_leaderboard_definition_t* leaderboard, uint32_t* num_leaderboards, rc_json_field_t* array_field) { + const rc_api_leaderboard_definition_t* first_leaderboard = leaderboard; rc_json_iterator_t iterator; size_t len; int result; @@ -280,6 +287,11 @@ static int rc_api_process_fetch_game_data_leaderboards(rc_api_response_t* respon ++leaderboard; } + if (leaderboard < first_leaderboard + *num_leaderboards) { + *num_leaderboards = (uint32_t)(leaderboard - first_leaderboard); + return RC_INVALID_VALUE; + } + return RC_OK; } @@ -356,7 +368,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon if (!response->achievements) return RC_OUT_OF_MEMORY; - result = rc_api_process_fetch_game_data_achievements(&response->response, response->achievements, &array_field); + result = rc_api_process_fetch_game_data_achievements(&response->response, response->achievements, &response->num_achievements, &array_field); if (result != RC_OK) return result; } @@ -369,7 +381,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon if (!response->leaderboards) return RC_OUT_OF_MEMORY; - result = rc_api_process_fetch_game_data_leaderboards(&response->response, response->leaderboards, &array_field); + result = rc_api_process_fetch_game_data_leaderboards(&response->response, response->leaderboards, &response->num_leaderboards, &array_field); if (result != RC_OK) return result; } @@ -415,7 +427,9 @@ int rc_api_init_fetch_game_sets_request_hosted(rc_api_request_t* request, static int rc_api_process_fetch_game_sets_achievement_sets(rc_api_fetch_game_sets_response_t* response, rc_api_achievement_set_definition_t* subset, + uint32_t* num_subsets, rc_json_field_t* subset_array_field) { + const rc_api_achievement_set_definition_t* first_subset = subset; rc_json_iterator_t iterator; rc_json_field_t array_field; size_t len; @@ -487,7 +501,7 @@ static int rc_api_process_fetch_game_sets_achievement_sets(rc_api_fetch_game_set if (!subset->achievements) return RC_OUT_OF_MEMORY; - result = rc_api_process_fetch_game_data_achievements(&response->response, subset->achievements, &array_field); + result = rc_api_process_fetch_game_data_achievements(&response->response, subset->achievements, &subset->num_achievements, &array_field); if (result != RC_OK) return result; } @@ -500,7 +514,7 @@ static int rc_api_process_fetch_game_sets_achievement_sets(rc_api_fetch_game_set if (!subset->leaderboards) return RC_OUT_OF_MEMORY; - result = rc_api_process_fetch_game_data_leaderboards(&response->response, subset->leaderboards, &array_field); + result = rc_api_process_fetch_game_data_leaderboards(&response->response, subset->leaderboards, &subset->num_leaderboards, &array_field); if (result != RC_OK) return result; } @@ -508,6 +522,11 @@ static int rc_api_process_fetch_game_sets_achievement_sets(rc_api_fetch_game_set ++subset; } + if (subset < first_subset + *num_subsets) { + *num_subsets = (uint32_t)(subset - first_subset); + return RC_INVALID_VALUE; + } + return RC_OK; } @@ -558,7 +577,7 @@ int rc_api_process_fetch_game_sets_server_response(rc_api_fetch_game_sets_respon if (!response->sets) return RC_OUT_OF_MEMORY; - result = rc_api_process_fetch_game_sets_achievement_sets(response, response->sets, &array_field); + result = rc_api_process_fetch_game_sets_achievement_sets(response, response->sets, &response->num_sets, &array_field); if (result != RC_OK) return result; } @@ -900,6 +919,11 @@ int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entr ++entry; } + + if (entry < response->top_entries + response->num_top_entries) { + response->num_top_entries = (uint32_t)(entry - response->top_entries); + return RC_INVALID_VALUE; + } } return RC_OK; diff --git a/src/rapi/rc_api_user.c b/src/rapi/rc_api_user.c index 6aa09af2..bef745a6 100644 --- a/src/rapi/rc_api_user.c +++ b/src/rapi/rc_api_user.c @@ -188,6 +188,11 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t ++unlock; } + + if (unlock < response->unlocks + response->num_unlocks) { + response->num_unlocks = (uint32_t)(unlock - response->unlocks); + return RC_INVALID_VALUE; + } } if (rc_json_get_optional_array(&response->num_hardcore_unlocks, &array_field, &fields[3], "HardcoreUnlocks") && response->num_hardcore_unlocks) { @@ -208,6 +213,11 @@ int rc_api_process_start_session_server_response(rc_api_start_session_response_t ++unlock; } + + if (unlock < response->hardcore_unlocks + response->num_hardcore_unlocks) { + response->num_hardcore_unlocks = (uint32_t)(unlock - response->hardcore_unlocks); + return RC_INVALID_VALUE; + } } rc_json_get_optional_timet(&response->server_now, &fields[4], "ServerNow", 0); @@ -363,6 +373,11 @@ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_us ++user; } + + if (user < response->users + response->num_users) { + response->num_users = (uint32_t)(user - response->users); + return RC_INVALID_VALUE; + } } return RC_OK; diff --git a/test/rapi/test_rc_api_user.c b/test/rapi/test_rc_api_user.c index ecbd6449..ba7c60c8 100644 --- a/test/rapi/test_rc_api_user.c +++ b/test/rapi/test_rc_api_user.c @@ -143,6 +143,29 @@ static void test_process_start_session_response_invalid_credentials() rc_api_destroy_start_session_response(&start_session_response); } +static void test_process_start_session_response_integer_array() +{ + rc_api_start_session_response_t start_session_response; + /* HardcoreUnlocks should be an array of objects, not an array of integers */ + const char* server_response = "{\"Success\":true,\"HardcoreUnlocks\":[111,112,113]," + "\"Unlocks\":[" + "{\"ID\":114,\"When\":1234567840}" + "],\"ServerNow\":1234577777}"; + + memset(&start_session_response, 0, sizeof(start_session_response)); + + ASSERT_NUM_EQUALS(rc_api_process_start_session_response(&start_session_response, server_response), RC_INVALID_VALUE); + ASSERT_NUM_EQUALS(start_session_response.response.succeeded, 1); + ASSERT_PTR_NULL(start_session_response.response.error_message); + ASSERT_NUM_EQUALS(start_session_response.num_unlocks, 1); + ASSERT_NUM_EQUALS(start_session_response.unlocks[0].achievement_id, 114); + ASSERT_TIMET_EQUALS(start_session_response.unlocks[0].when, 1234567840); + ASSERT_NUM_EQUALS(start_session_response.num_hardcore_unlocks, 0); + ASSERT_TIMET_EQUALS(start_session_response.server_now, 0); /* processing stopped before it got here, but caller should ignore everything based on the RC_INVALID_VALUE */ + + rc_api_destroy_start_session_response(&start_session_response); +} + static void test_init_login_request_password() { rc_api_login_request_t login_request; @@ -657,7 +680,7 @@ static void test_init_fetch_user_unlocks_request_hardcore() rc_api_destroy_request(&request); } -static void test_init_fetch_user_unlocks_response_empty_array() +static void test_process_fetch_user_unlocks_response_empty_array() { rc_api_fetch_user_unlocks_response_t fetch_user_unlocks_response; const char* server_response = "{\"Success\":true,\"UserUnlocks\":[],\"GameID\":11277,\"HardcoreMode\":false}"; @@ -672,7 +695,7 @@ static void test_init_fetch_user_unlocks_response_empty_array() rc_api_destroy_fetch_user_unlocks_response(&fetch_user_unlocks_response); } -static void test_init_fetch_user_unlocks_response_invalid_credentials() +static void test_process_fetch_user_unlocks_response_invalid_credentials() { rc_api_fetch_user_unlocks_response_t fetch_user_unlocks_response; const char* server_response = "{\"Success\":false,\"Error\":\"Credentials invalid (0)\"}"; @@ -687,7 +710,7 @@ static void test_init_fetch_user_unlocks_response_invalid_credentials() rc_api_destroy_fetch_user_unlocks_response(&fetch_user_unlocks_response); } -static void test_init_fetch_user_unlocks_response_one_item() +static void test_process_fetch_user_unlocks_response_one_item() { rc_api_fetch_user_unlocks_response_t fetch_user_unlocks_response; const char* server_response = "{\"Success\":true,\"UserUnlocks\":[1234],\"GameID\":11277,\"HardcoreMode\":false}"; @@ -703,7 +726,7 @@ static void test_init_fetch_user_unlocks_response_one_item() rc_api_destroy_fetch_user_unlocks_response(&fetch_user_unlocks_response); } -static void test_init_fetch_user_unlocks_response_several_items() +static void test_process_fetch_user_unlocks_response_several_items() { rc_api_fetch_user_unlocks_response_t fetch_user_unlocks_response; const char* server_response = "{\"Success\":true,\"UserUnlocks\":[1,2,3,4],\"GameID\":11277,\"HardcoreMode\":false}"; @@ -739,7 +762,7 @@ static void test_init_fetch_followed_users_request() rc_api_destroy_request(&request); } -static void test_init_fetch_followed_users_response_empty_array() +static void test_process_fetch_followed_users_response_empty_array() { rc_api_fetch_followed_users_response_t fetch_followed_users_response; rc_api_server_response_t server_response; @@ -758,7 +781,7 @@ static void test_init_fetch_followed_users_response_empty_array() rc_api_destroy_fetch_followed_users_response(&fetch_followed_users_response); } -static void test_init_fetch_followed_users_response_invalid_credentials() +static void test_process_fetch_followed_users_response_invalid_credentials() { rc_api_fetch_followed_users_response_t fetch_followed_users_response; rc_api_server_response_t server_response; @@ -777,7 +800,7 @@ static void test_init_fetch_followed_users_response_invalid_credentials() rc_api_destroy_fetch_followed_users_response(&fetch_followed_users_response); } -static void test_init_fetch_followed_users_response_several_items() +static void test_process_fetch_followed_users_response_several_items() { rc_api_fetch_followed_users_response_t fetch_followed_users_response; rc_api_server_response_t server_response; @@ -832,7 +855,7 @@ static void test_init_fetch_followed_users_response_several_items() rc_api_destroy_fetch_followed_users_response(&fetch_followed_users_response); } -static void test_init_fetch_followed_users_response_avatar_updated() +static void test_process_fetch_followed_users_response_avatar_updated() { rc_api_fetch_followed_users_response_t fetch_followed_users_response; rc_api_server_response_t server_response; @@ -894,7 +917,7 @@ static void test_init_fetch_all_user_progress_request() rc_api_destroy_request(&request); } -static void test_init_fetch_all_user_progress_response_empty_array() +static void test_process_fetch_all_user_progress_response_empty_array() { rc_api_fetch_all_user_progress_response_t fetch_all_user_progress_response; rc_api_server_response_t response_obj; @@ -917,7 +940,7 @@ static void test_init_fetch_all_user_progress_response_empty_array() rc_api_destroy_fetch_all_user_progress_response(&fetch_all_user_progress_response); } -static void test_init_fetch_all_user_progress_response_invalid_credentials() +static void test_process_fetch_all_user_progress_response_invalid_credentials() { rc_api_fetch_all_user_progress_response_t fetch_all_user_progress_response; rc_api_server_response_t response_obj; @@ -941,7 +964,7 @@ static void test_init_fetch_all_user_progress_response_invalid_credentials() rc_api_destroy_fetch_all_user_progress_response(&fetch_all_user_progress_response); } -static void test_init_fetch_all_user_progress_response_one_item() +static void test_process_fetch_all_user_progress_response_one_item() { rc_api_fetch_all_user_progress_response_t fetch_all_user_progress_response; rc_api_server_response_t response_obj; @@ -967,7 +990,7 @@ static void test_init_fetch_all_user_progress_response_one_item() rc_api_destroy_fetch_all_user_progress_response(&fetch_all_user_progress_response); } -static void test_init_fetch_all_user_progress_response_several_items() +static void test_process_fetch_all_user_progress_response_several_items() { rc_api_fetch_all_user_progress_response_t fetch_all_user_progress_response; rc_api_server_response_t response_obj; @@ -1021,6 +1044,7 @@ void test_rapi_user(void) { TEST(test_process_start_session_response_legacy); TEST(test_process_start_session_response); TEST(test_process_start_session_response_invalid_credentials); + TEST(test_process_start_session_response_integer_array); /* login */ TEST(test_init_login_request_password); @@ -1049,26 +1073,26 @@ void test_rapi_user(void) { TEST(test_init_fetch_user_unlocks_request_non_hardcore); TEST(test_init_fetch_user_unlocks_request_hardcore); - TEST(test_init_fetch_user_unlocks_response_empty_array); - TEST(test_init_fetch_user_unlocks_response_invalid_credentials); - TEST(test_init_fetch_user_unlocks_response_one_item); - TEST(test_init_fetch_user_unlocks_response_several_items); + TEST(test_process_fetch_user_unlocks_response_empty_array); + TEST(test_process_fetch_user_unlocks_response_invalid_credentials); + TEST(test_process_fetch_user_unlocks_response_one_item); + TEST(test_process_fetch_user_unlocks_response_several_items); /* followed users */ TEST(test_init_fetch_followed_users_request); - TEST(test_init_fetch_followed_users_response_empty_array); - TEST(test_init_fetch_followed_users_response_invalid_credentials); - TEST(test_init_fetch_followed_users_response_several_items); - TEST(test_init_fetch_followed_users_response_avatar_updated); + TEST(test_process_fetch_followed_users_response_empty_array); + TEST(test_process_fetch_followed_users_response_invalid_credentials); + TEST(test_process_fetch_followed_users_response_several_items); + TEST(test_process_fetch_followed_users_response_avatar_updated); /* all user progress */ TEST(test_init_fetch_all_user_progress_request); - TEST(test_init_fetch_all_user_progress_response_empty_array); - TEST(test_init_fetch_all_user_progress_response_invalid_credentials); - TEST(test_init_fetch_all_user_progress_response_one_item); - TEST(test_init_fetch_all_user_progress_response_several_items); + TEST(test_process_fetch_all_user_progress_response_empty_array); + TEST(test_process_fetch_all_user_progress_response_invalid_credentials); + TEST(test_process_fetch_all_user_progress_response_one_item); + TEST(test_process_fetch_all_user_progress_response_several_items); TEST_SUITE_END(); } From 9e9b95a14b6bb9671b3669ab97f5eb735170fc08 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Thu, 1 Oct 2026 17:00:55 -0600 Subject: [PATCH 2/2] check body_length instead of looking for null terminator --- src/rapi/rc_api_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rapi/rc_api_common.c b/src/rapi/rc_api_common.c index 3b352860..78796c0e 100644 --- a/src/rapi/rc_api_common.c +++ b/src/rapi/rc_api_common.c @@ -365,7 +365,7 @@ int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_serv return RC_NO_RESPONSE; } - if (!server_response->body || !*server_response->body) { + if (!server_response->body || !server_response->body_length) { /* expect valid HTTP status codes to have bodies that we can extract the message from, * but provide some default messages in case they don't. */ switch (server_response->http_status_code) {