From 295dc9024f371a7a75885ebb611a9ba3f6ce65a5 Mon Sep 17 00:00:00 2001 From: althafvly Date: Wed, 4 Oct 2023 13:00:06 +0530 Subject: [PATCH 1/3] Update: add json validator --- .../updater/UpdatesCheckReceiver.java | 15 +- .../lineageos/updater/misc/JsonValidator.java | 161 ++++++++++++++++++ src/org/lineageos/updater/misc/Utils.java | 13 +- 3 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/org/lineageos/updater/misc/JsonValidator.java diff --git a/src/org/lineageos/updater/UpdatesCheckReceiver.java b/src/org/lineageos/updater/UpdatesCheckReceiver.java index 9ac37a25..ad83d289 100644 --- a/src/org/lineageos/updater/UpdatesCheckReceiver.java +++ b/src/org/lineageos/updater/UpdatesCheckReceiver.java @@ -33,6 +33,7 @@ import androidx.preference.PreferenceManager; import org.json.JSONException; import org.lineageos.updater.download.DownloadClient; import org.lineageos.updater.misc.Constants; +import org.lineageos.updater.misc.JsonValidator; import org.lineageos.updater.misc.Utils; import java.io.File; @@ -67,6 +68,13 @@ public class UpdatesCheckReceiver extends BroadcastReceiver { Utils.cleanupDownloadsDir(context); } + final File json = Utils.getCachedUpdateList(context); + boolean isValidatedJsonFile = JsonValidator.validateJsonFile(json); + if (!isValidatedJsonFile && json.exists()) { + Log.i(TAG, "Removing cached json file due validation failure"); + json.delete(); + } + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); @@ -85,7 +93,6 @@ public class UpdatesCheckReceiver extends BroadcastReceiver { return; } - final File json = Utils.getCachedUpdateList(context); final File jsonNew = new File(json.getAbsolutePath() + UUID.randomUUID()); String url = Utils.getServerURL(context); DownloadClient.DownloadCallback callback = new DownloadClient.DownloadCallback() { @@ -103,6 +110,12 @@ public class UpdatesCheckReceiver extends BroadcastReceiver { @Override public void onSuccess(File destination) { try { + boolean isValidated = JsonValidator.validateJsonFile(jsonNew); + if (!isValidated) { + Log.i(TAG, "Could not parse list, scheduling new check"); + scheduleUpdatesCheck(context); + return; + } if (json.exists() && Utils.checkForNewUpdates(jsonNew)) { showNotification(context); updateRepeatingUpdatesCheck(context); diff --git a/src/org/lineageos/updater/misc/JsonValidator.java b/src/org/lineageos/updater/misc/JsonValidator.java new file mode 100644 index 00000000..a105c151 --- /dev/null +++ b/src/org/lineageos/updater/misc/JsonValidator.java @@ -0,0 +1,161 @@ +package org.lineageos.updater.misc; + +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class JsonValidator { + + private static final String TAG = "JsonValidator" ; + private static final String[] jsonMainFields = { + "error", + "id", + "response" + }; + private static final String[] requiredFields = { + "api_level", + "url", + "timestamp", + "md5sum", + "channel", + "filename", + "romtype", + "version", + "display_version", + "android_version", + "id" + }; + private static final String[] optionalFields = { + "changes", + "pre_version" + }; + + public static boolean validateJsonFile(File jsonFile) { + try { + if (!jsonFile.exists()) { + Log.i(TAG, "Unable to locate json file"); + return false; + } + + // Read the JSON data from the file + BufferedReader reader = new BufferedReader(new FileReader(jsonFile)); + StringBuilder jsonString = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + jsonString.append(line); + } + reader.close(); + + List missingRequiredFields = new ArrayList<>(); + JSONObject jsonObject = new JSONObject(jsonString.toString()); + for (String field : jsonMainFields) { + if (!isMainFieldValid(jsonObject, field)) { + missingRequiredFields.add(field); + } + } + + if (!missingRequiredFields.isEmpty()) { + Log.i(TAG, "Missing or invalid required field in response object: " + + missingRequiredFields); + return false; + } + } catch (IOException | JSONException e) { + Log.i(TAG, "Unable to parse the json file:" + e); + } + return false; + } + + public static boolean validateResponseObject(JSONObject responseObject) { + List missingRequiredFields = new ArrayList<>(); + List missingOptionalFields = new ArrayList<>(); + + for (String field : requiredFields) { + if (!isObjectFieldValid(responseObject, field)) { + missingRequiredFields.add(field); + } + } + + for (String field : optionalFields) { + if (!isObjectFieldValid(responseObject, field)) { + missingOptionalFields.add(field); + } + } + + String filename = responseObject.optString("filename", ""); + if (!missingRequiredFields.isEmpty()) { + if (!filename.isEmpty()) { + Log.i(TAG, "Missing or invalid required field in response object for " + + filename + ": " + missingRequiredFields); + } + return false; + } else if (!missingOptionalFields.isEmpty() && !filename.isEmpty()) { + Log.i(TAG, "Missing or invalid optional field in response object for " + + filename + ": " + missingOptionalFields); + } + + return true; // All required fields are present and valid + } + + private static boolean isMainFieldValid(JSONObject jsonObject, String field) { + if (!jsonObject.has(field) || jsonObject.isNull(field)) { + return false; + } + + String value = jsonObject.optString(field, ""); // Return empty string if field doesn't exist + + switch (field) { + case "id": + case "error": + // Allow null, as it's a valid value + return true; + case "response": + // Check if it's an array with a length >= 0 + return jsonObject.optJSONArray(field) != null + && jsonObject.optJSONArray(field).length() >= 0; + default: + return !value.isEmpty(); + } + } + + private static boolean isObjectFieldValid(JSONObject jsonObject, String field) { + if (!jsonObject.has(field) || jsonObject.isNull(field)) { + return false; + } + + String value = jsonObject.optString(field, ""); // Return empty string if field doesn't exist + + switch (field) { + case "datetime": + case "timestamp": + case "size": + case "api_level": + return Utils.isInteger(value); + case "is_upgrade_supported": + return "true".equals(value) || "false".equals(value); + case "android_version": + // Check if it's a valid Android version number + return value.matches("^(\\d+)$|^(\\d+\\.\\d+)$|^(\\d+\\.\\d+\\.\\d+)$"); + case "md5sum": + // Check if it's a valid MD5 checksum + return value.matches("^[a-fA-F0-9]{32}$"); + case "filename": + return value.endsWith(".zip"); + case "id": + // Check if it's a valid SHA-256 checksum + return value.matches("^[a-fA-F0-9]{64}$"); + case "url": + // Check if it's a valid URL format + return value.startsWith("http://") || value.startsWith("https://"); + default: + return !value.isEmpty(); + } + } +} diff --git a/src/org/lineageos/updater/misc/Utils.java b/src/org/lineageos/updater/misc/Utils.java index 6423b13f..a0bfaac3 100644 --- a/src/org/lineageos/updater/misc/Utils.java +++ b/src/org/lineageos/updater/misc/Utils.java @@ -202,7 +202,9 @@ public class Utils { } try { UpdateInfo update = parseJsonUpdate(updatesList.getJSONObject(i)); - if (!compatibleOnly || isCompatible(update)) { + boolean isValidated = JsonValidator.validateResponseObject( + updatesList.getJSONObject(i)); + if (isValidated && (!compatibleOnly || isCompatible(update))) { updates.add(update); } else { Log.d(TAG, "Ignoring incompatible update " + update.getName()); @@ -512,4 +514,13 @@ public class Utils { context.getResources().getInteger(R.integer.battery_ok_percentage_discharging); return percent >= required; } + + public static boolean isInteger(String value) { + try { + Integer.parseInt(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } } -- GitLab From 4e0adb6688217c742965d70d74a2288d4e7a9d3c Mon Sep 17 00:00:00 2001 From: althafvly Date: Fri, 13 Oct 2023 14:19:00 +0530 Subject: [PATCH 2/3] Updater: validate update object early --- src/org/lineageos/updater/misc/Utils.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/org/lineageos/updater/misc/Utils.java b/src/org/lineageos/updater/misc/Utils.java index a0bfaac3..1aef4ec7 100644 --- a/src/org/lineageos/updater/misc/Utils.java +++ b/src/org/lineageos/updater/misc/Utils.java @@ -201,10 +201,14 @@ public class Utils { continue; } try { - UpdateInfo update = parseJsonUpdate(updatesList.getJSONObject(i)); boolean isValidated = JsonValidator.validateResponseObject( updatesList.getJSONObject(i)); - if (isValidated && (!compatibleOnly || isCompatible(update))) { + if (!isValidated) { + Log.d(TAG, "Ignoring incompatible update"); + continue; + } + UpdateInfo update = parseJsonUpdate(updatesList.getJSONObject(i)); + if (!compatibleOnly || isCompatible(update)) { updates.add(update); } else { Log.d(TAG, "Ignoring incompatible update " + update.getName()); -- GitLab From 80d7bceaef88bdaa718338bce2dd7de001c55bff Mon Sep 17 00:00:00 2001 From: althafvly Date: Fri, 13 Oct 2023 16:38:54 +0530 Subject: [PATCH 3/3] Updater: Check if value is empty first --- src/org/lineageos/updater/misc/JsonValidator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/org/lineageos/updater/misc/JsonValidator.java b/src/org/lineageos/updater/misc/JsonValidator.java index a105c151..3e3bcc3a 100644 --- a/src/org/lineageos/updater/misc/JsonValidator.java +++ b/src/org/lineageos/updater/misc/JsonValidator.java @@ -131,6 +131,9 @@ public class JsonValidator { } String value = jsonObject.optString(field, ""); // Return empty string if field doesn't exist + if (value.isEmpty()) { + return false; + } switch (field) { case "datetime": @@ -155,7 +158,7 @@ public class JsonValidator { // Check if it's a valid URL format return value.startsWith("http://") || value.startsWith("https://"); default: - return !value.isEmpty(); + return true; } } } -- GitLab