浏览代码

fix: Validate nickname and convert to proper UTF-8 before passing to JNI

The JNI "modified" UTF-8 isn't what SGP.22 mandates. Let's encode
properly, validate the length, and pass the string as a C
null-terminated string directly over JNI.

This also introduces new exceptions that are exposed via UI as Toasts.
Peter Cai 1 年之前
父节点
当前提交
3b7bd8b31e

+ 2 - 1
app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt

@@ -54,8 +54,9 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) :
 
     override fun euiccMemoryReset() = lpa.euiccMemoryReset()
 
-    override fun setNickname(iccid: String, nickname: String): Boolean =
+    override fun setNickname(iccid: String, nickname: String) {
         lpa.setNickname(iccid, nickname)
+    }
 
     override fun close() = lpa.close()
 

+ 1 - 5
app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt

@@ -414,16 +414,12 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
             getString(R.string.task_profile_rename_failure),
             R.drawable.ic_task_rename
         ) {
-            val res = euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
+            euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
                 channel.lpa.setNickname(
                     iccid,
                     name
                 )
             }
-
-            if (!res) {
-                throw RuntimeException("Profile not renamed")
-            }
         }
 
     fun launchProfileDeleteTask(

+ 38 - 9
app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt

@@ -14,6 +14,7 @@ import im.angry.openeuicc.common.R
 import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
 import im.angry.openeuicc.util.*
 import kotlinx.coroutines.launch
+import net.typeblog.lpac_jni.LocalProfileAssistant
 
 class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker {
     companion object {
@@ -95,21 +96,49 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
         lifecycleScope.launch {
             ensureEuiccChannelManager()
             euiccChannelManagerService.waitForForegroundTask()
-            euiccChannelManagerService.launchProfileRenameTask(
+            val res = euiccChannelManagerService.launchProfileRenameTask(
                 slotId,
                 portId,
                 requireArguments().getString("iccid")!!,
                 name
             ).waitDone()
 
-            if (parentFragment is EuiccProfilesChangedListener) {
-                (parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
-            }
-
-            try {
-                dismiss()
-            } catch (e: IllegalStateException) {
-                // Ignored
+            when (res) {
+                is LocalProfileAssistant.ProfileNameTooLongException -> {
+                    Toast.makeText(
+                        requireContext(),
+                        R.string.profile_rename_too_long,
+                        Toast.LENGTH_LONG
+                    ).show()
+                }
+
+                is LocalProfileAssistant.ProfileNameIsInvalidUTF8Exception -> {
+                    Toast.makeText(
+                        requireContext(),
+                        R.string.profile_rename_encoding_error,
+                        Toast.LENGTH_LONG
+                    ).show()
+                }
+
+                is Throwable -> {
+                    Toast.makeText(
+                        requireContext(),
+                        R.string.profile_rename_failure,
+                        Toast.LENGTH_LONG
+                    ).show()
+                }
+
+                else -> {
+                    if (parentFragment is EuiccProfilesChangedListener) {
+                        (parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
+                    }
+
+                    try {
+                        dismiss()
+                    } catch (e: IllegalStateException) {
+                        // Ignored
+                    }
+                }
             }
         }
     }

+ 3 - 0
app-common/src/main/res/values/strings.xml

@@ -97,6 +97,9 @@
     <string name="logs_saved_message">Logs have been saved to the selected path. Would you like to share the log through another app?</string>
 
     <string name="profile_rename_new_name">New nickname</string>
+    <string name="profile_rename_encoding_error">Failed to encode nickname as UTF-8</string>
+    <string name="profile_rename_too_long">Nickname is too long</string>
+    <string name="profile_rename_failure">Unknown failure when renaming profile</string>
 
     <string name="profile_delete_confirm">Are you sure you want to delete the profile %s? This operation is irreversible.</string>
     <string name="profile_delete_confirm_input">Type \'%s\' here to confirm deletion</string>

+ 10 - 1
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt

@@ -12,6 +12,10 @@ interface LocalProfileAssistant {
         val lastApduException: Exception?,
     ) : Exception("Failed to download profile")
 
+    class ProfileRenameException() : Exception("Failed to rename profile")
+    class ProfileNameTooLongException() : Exception("Profile name too long")
+    class ProfileNameIsInvalidUTF8Exception() : Exception("Profile name is invalid UTF-8")
+
     val valid: Boolean
     val profiles: List<LocalProfileInfo>
     val notifications: List<LocalProfileNotification>
@@ -40,9 +44,14 @@ interface LocalProfileAssistant {
 
     fun euiccMemoryReset()
 
+    /**
+     * Nickname must be valid UTF-8 and shorter than 64 chars.
+     *
+     * May throw one of: ProfileRenameException, ProfileNameTooLongException, ProfileNameIsInvalidUTF8Exception
+     */
     fun setNickname(
         iccid: String, nickname: String
-    ): Boolean
+    )
 
     fun close()
 }

+ 1 - 1
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt

@@ -19,7 +19,7 @@ internal object LpacJni {
     external fun es10cEnableProfile(handle: Long, iccid: String, refresh: Boolean): Int
     external fun es10cDisableProfile(handle: Long, iccid: String, refresh: Boolean): Int
     external fun es10cDeleteProfile(handle: Long, iccid: String): Int
-    external fun es10cSetNickname(handle: Long, iccid: String, nick: String): Int
+    external fun es10cSetNickname(handle: Long, iccid: String, nickNullTerminated: ByteArray): Int
 
     // es10b
     external fun es10bListNotification(handle: Long): Long // A native pointer to a linked list. Handle with linked list-related methods below. May be 0 (null)

+ 17 - 2
libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt

@@ -239,8 +239,23 @@ class LocalProfileAssistantImpl(
         } == 0
 
     @Synchronized
-    override fun setNickname(iccid: String, nickname: String): Boolean =
-        LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0
+    override fun setNickname(iccid: String, nickname: String) {
+        val encoded = try {
+            Charsets.UTF_8.encode(nickname).array()
+        } catch (e: CharacterCodingException) {
+            throw LocalProfileAssistant.ProfileNameIsInvalidUTF8Exception()
+        }
+
+        if (encoded.size >= 64) {
+            throw LocalProfileAssistant.ProfileNameTooLongException()
+        }
+
+        val encodedNullTerminated = encoded + byteArrayOf(0)
+
+        if (LpacJni.es10cSetNickname(contextHandle, iccid, encodedNullTerminated) != 0) {
+            throw LocalProfileAssistant.ProfileRenameException()
+        }
+    }
 
     override fun euiccMemoryReset() {
         LpacJni.es10cEuiccMemoryReset(contextHandle)

+ 5 - 5
libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c

@@ -205,16 +205,16 @@ Java_net_typeblog_lpac_1jni_LpacJni_es10cDisableProfile(JNIEnv *env, jobject thi
 
 JNIEXPORT jint JNICALL
 Java_net_typeblog_lpac_1jni_LpacJni_es10cSetNickname(JNIEnv *env, jobject thiz, jlong handle,
-                                                     jstring iccid, jstring nick) {
+                                                     jstring iccid, jbyteArray nick) {
     struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
     const char *_iccid = NULL;
-    const char *_nick = NULL;
+    jbyte *_nick = NULL;
     int ret;
 
     _iccid = (*env)->GetStringUTFChars(env, iccid, NULL);
-    _nick = (*env)->GetStringUTFChars(env, nick, NULL);
-    ret = es10c_set_nickname(ctx, _iccid, _nick);
-    (*env)->ReleaseStringUTFChars(env, nick, _nick);
+    _nick = (*env)->GetByteArrayElements(env, nick, NULL);
+    ret = es10c_set_nickname(ctx, _iccid, (const char *) _nick);
+    (*env)->ReleaseByteArrayElements(env, nick, _nick, JNI_ABORT);
     (*env)->ReleaseStringUTFChars(env, iccid, _iccid);
     return ret;
 }