CVE-2023-46139 (C, published 2023-10-31T00:15Z)
Description: value: KernelSU is a Kernel based root solution for Android. Starting in version 0.6.1 and prior to version 0.7.0, if a KernelSU installed device is infected with a malware whose app signing block specially constructed, it can take over root privileges on the device. The vulnerable verification logic actually obtains the signature of the last block with an id of `0x7109871a`, while the verification logic during Android installation is to obtain the first one. In addition to the actual signature upgrade that has been fixed (KSU thought it was V2 but was actually V3), there is also the problem of actual signature downgrading (KSU thought it was V2 but was actually V1). Find a condition in the signature verification logic that will cause the signature not to be found error, and KernelSU does not implement the same conditions, so KSU thinks there is a V2 signature, but the APK signature verification actually uses the V1 signature. This issue is fixed in version 0.7.0. As workarounds, keep the KernelSU manager installed and avoid installing unknown apps.
Severity: nan (CVSS v3 base score: 5.7)
Weakness: CWE-863 \u2014 Incorrect Authorization
The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
Repository: https://github.com/tiann/KernelSU
Files changed:
kernel/Makefile
kernel/apk_sign.c
kernel/manager.c
Commit message: Merge pull request from GHSA-86cp-3prf-pwqq

* kernel: deny v2 signature blocks with incorrect number

* kernel: reject v1 signature

* kernel: enforce manager package name at compile time

* kernel: don't specific package name in source code, use it in ci

Vulnerable code:
		unsigned char *digest)
			unsigned expected_size, const char* expected_sha256)
		#define CERT_MAX_LENGTH 1024
		pr_info("sha256: %s, expected: %s\n", hash_str, expected_sha256);
static __always_inline bool
check_v2_signature(char *path, unsigned expected_size, const char *expected_sha256)
			v2_signing_valid = check_block(fp, &size4, &pos, &offset,
						  expected_size, expected_sha256);

Fixed code:
ifdef KSU_MANAGER_PACKAGE
ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
$(info -- KernelSU Manager package name: $(KSU_MANAGER_PACKAGE))
endif

		      unsigned char *digest)
			unsigned expected_size, const char *expected_sha256)
#define CERT_MAX_LENGTH 1024
		pr_info("sha256: %s, expected: %s\n", hash_str,
			expected_sha256);
struct zip_entry_header {
	uint32_t signature;
	uint16_t version;
	uint16_t flags;
	uint16_t compression;
	uint16_t mod_time;
	uint16_t mod_date;
	uint32_t crc32;
	uint32_t compressed_size;
	uint32_t uncompressed_size;
	uint16_t file_name_length;
	uint16_t extra_field_length;
} __attribute__((packed));

// This is a necessary but not sufficient condition, but it is enough for us
static bool has_v1_signature_file(struct file *fp)
{
	struct zip_entry_header header;
	const char MANIFEST[] = "META-INF/MANIFEST.MF";

	loff_t pos = 0;

	while (ksu_kernel_read_compat(fp, &header,
				      sizeof(struct zip_entry_header), &pos) ==
	       sizeof(struct zip_entry_header)) {
		if (header.signature != 0x04034b50) {
			// ZIP magic: 'PK'
			return false;
		}
		// Read the entry file name
		if (header.file_name_length == sizeof(MANIFEST) - 1) {
			char fileName[sizeof(MANIFEST)];
			ksu_kernel_read_compat(fp, fileName,
					       header.file_name_length, &pos);
			fileName[header.file_name_length] = '\0';

			// Check if the entry matches META-INF/MANIFEST.MF
			if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) ==
			    0) {
				return true;
			}
		} else {
			// Skip the entry file name
			pos += header.file_name_length;
		}

		// Skip to the next entry
		pos += header.extra_field_length + header.compressed_size;
	}

	return false;
}

static __always_inline bool check_v2_signature(char *path,
					       unsigned expected_size,
					       const char *expected_sha256)
	int v2_signing_blocks = 0;
			v2_signing_blocks++;
			v2_signing_valid =
				check_block(fp, &size4, &pos, &offset,
					    expected_size, expected_sha256);
	if (v2_signing_blocks != 1) {
		pr_err("Unexpected v2 signature count: %d\n",
		       v2_signing_blocks);
		v2_signing_valid = false;
	}

	if (v2_signing_valid) {
		int has_v1_signing = has_v1_signature_file(fp);
		if (has_v1_signing) {
			pr_err("Unexpected v1 signature scheme found!\n");
			filp_close(fp, 0);
			return false;
		}
	}
#ifdef KSU_MANAGER_PACKAGE
	// pkg is `/<real package>`
	if (strncmp(pkg + 1, KSU_MANAGER_PACKAGE,
		    sizeof(KSU_MANAGER_PACKAGE) - 1) != 0) {
		pr_info("manager package is inconsistent with kernel build: %s\n",
			KSU_MANAGER_PACKAGE);
		return false;
	}
#endif