Raymond Mao | 7b3dfd0 | 2024-10-03 14:50:32 -0700 | [diff] [blame^] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * PKCS#7 parser using MbedTLS PKCS#7 library |
| 4 | * |
| 5 | * Copyright (c) 2024 Linaro Limited |
| 6 | * Author: Raymond Mao <raymond.mao@linaro.org> |
| 7 | */ |
| 8 | |
| 9 | #include <log.h> |
| 10 | #include <linux/kernel.h> |
| 11 | #include <linux/err.h> |
| 12 | #include <crypto/public_key.h> |
| 13 | #include <crypto/pkcs7_parser.h> |
| 14 | |
| 15 | static void pkcs7_free_mbedtls_ctx(struct pkcs7_mbedtls_ctx *ctx) |
| 16 | { |
| 17 | if (ctx) { |
| 18 | kfree(ctx->content_data); |
| 19 | kfree(ctx); |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | static void pkcs7_free_sinfo_mbedtls_ctx(struct pkcs7_sinfo_mbedtls_ctx *ctx) |
| 24 | { |
| 25 | if (ctx) { |
| 26 | kfree(ctx->authattrs_data); |
| 27 | kfree(ctx->content_data_digest); |
| 28 | kfree(ctx); |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | /* |
| 33 | * Parse Authenticate Attributes |
| 34 | * TODO: Shall we consider to integrate decoding of authenticate attribute into |
| 35 | * MbedTLS library? |
| 36 | * |
| 37 | * There are two kinds of structure for the Authenticate Attributes being used |
| 38 | * in U-Boot. |
| 39 | * |
| 40 | * Type 1 - contains in a PE/COFF EFI image: |
| 41 | * |
| 42 | * [C.P.0] { |
| 43 | * U.P.SEQUENCE { |
| 44 | * U.P.OBJECTIDENTIFIER 1.2.840.113549.1.9.3 (OID_contentType) |
| 45 | * U.P.SET { |
| 46 | * U.P.OBJECTIDENTIFIER 1.3.6.1.4.1.311.2.1.4 (OID_msIndirectData) |
| 47 | * } |
| 48 | * } |
| 49 | * U.P.SEQUENCE { |
| 50 | * U.P.OBJECTIDENTIFIER 1.2.840.113549.1.9.5 (OID_signingTime) |
| 51 | * U.P.SET { |
| 52 | * U.P.UTCTime '<siging_time>' |
| 53 | * } |
| 54 | * } |
| 55 | * U.P.SEQUENCE { |
| 56 | * U.P.OBJECTIDENTIFIER 1.2.840.113549.1.9.4 (OID_messageDigest) |
| 57 | * U.P.SET { |
| 58 | * U.P.OCTETSTRING <digest> |
| 59 | * } |
| 60 | * } |
| 61 | * U.P.SEQUENCE { |
| 62 | * U.P.OBJECTIDENTIFIER 1.2.840.113549.1.9.15 (OID_smimeCapabilites) |
| 63 | * U.P.SET { |
| 64 | * U.P.SEQUENCE { |
| 65 | * <...> |
| 66 | * } |
| 67 | * } |
| 68 | * } |
| 69 | * } |
| 70 | * |
| 71 | * Type 2 - contains in an EFI Capsule: |
| 72 | * |
| 73 | * [C.P.0] { |
| 74 | * U.P.SEQUENCE { |
| 75 | * U.P.OBJECTIDENTIFIER 1.2.840.113549.1.9.3 (OID_contentType) |
| 76 | * U.P.SET { |
| 77 | * U.P.OBJECTIDENTIFIER 1.2.840.113549.1.7.1 (OID_data) |
| 78 | * } |
| 79 | * } |
| 80 | * U.P.SEQUENCE { |
| 81 | * U.P.OBJECTIDENTIFIER 1.2.840.113549.1.9.5 (OID_signingTime) |
| 82 | * U.P.SET { |
| 83 | * U.P.UTCTime '<siging_time>' |
| 84 | * } |
| 85 | * } |
| 86 | * U.P.SEQUENCE { |
| 87 | * U.P.OBJECTIDENTIFIER 1.2.840.113549.1.9.4 (OID_messageDigest) |
| 88 | * U.P.SET { |
| 89 | * U.P.OCTETSTRING <digest> |
| 90 | * } |
| 91 | * } |
| 92 | *} |
| 93 | * |
| 94 | * Note: |
| 95 | * They have different Content Type (OID_msIndirectData or OID_data). |
| 96 | * OID_smimeCapabilites only exists in a PE/COFF EFI image. |
| 97 | */ |
| 98 | static int authattrs_parse(struct pkcs7_message *msg, void *aa, size_t aa_len, |
| 99 | struct pkcs7_signed_info *sinfo) |
| 100 | { |
| 101 | unsigned char *p = aa; |
| 102 | unsigned char *end = (unsigned char *)aa + aa_len; |
| 103 | size_t len = 0; |
| 104 | int ret; |
| 105 | unsigned char *inner_p; |
| 106 | size_t seq_len = 0; |
| 107 | |
| 108 | ret = mbedtls_asn1_get_tag(&p, end, &seq_len, |
| 109 | MBEDTLS_ASN1_CONTEXT_SPECIFIC | |
| 110 | MBEDTLS_ASN1_CONSTRUCTED); |
| 111 | if (ret) |
| 112 | return ret; |
| 113 | |
| 114 | while (!mbedtls_asn1_get_tag(&p, end, &seq_len, |
| 115 | MBEDTLS_ASN1_CONSTRUCTED | |
| 116 | MBEDTLS_ASN1_SEQUENCE)) { |
| 117 | inner_p = p; |
| 118 | ret = mbedtls_asn1_get_tag(&inner_p, p + seq_len, &len, |
| 119 | MBEDTLS_ASN1_OID); |
| 120 | if (ret) |
| 121 | return ret; |
| 122 | |
| 123 | if (!MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS9_CONTENTTYPE, inner_p, len)) { |
| 124 | inner_p += len; |
| 125 | ret = mbedtls_asn1_get_tag(&inner_p, p + seq_len, &len, |
| 126 | MBEDTLS_ASN1_CONSTRUCTED | |
| 127 | MBEDTLS_ASN1_SET); |
| 128 | if (ret) |
| 129 | return ret; |
| 130 | |
| 131 | ret = mbedtls_asn1_get_tag(&inner_p, p + seq_len, &len, |
| 132 | MBEDTLS_ASN1_OID); |
| 133 | if (ret) |
| 134 | return ret; |
| 135 | |
| 136 | /* |
| 137 | * We should only support 1.2.840.113549.1.7.1 (OID_data) |
| 138 | * for PKCS7 DATA that is used in EFI Capsule and |
| 139 | * 1.3.6.1.4.1.311.2.1.4 (OID_msIndirectData) for |
| 140 | * MicroSoft Authentication Code that is used in EFI |
| 141 | * Secure Boot. |
| 142 | */ |
| 143 | if (MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_MICROSOFT_INDIRECTDATA, |
| 144 | inner_p, len) && |
| 145 | MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS7_DATA, |
| 146 | inner_p, len)) |
| 147 | return -EINVAL; |
| 148 | |
| 149 | if (__test_and_set_bit(sinfo_has_content_type, &sinfo->aa_set)) |
| 150 | return -EINVAL; |
| 151 | } else if (!MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS9_MESSAGEDIGEST, inner_p, |
| 152 | len)) { |
| 153 | inner_p += len; |
| 154 | ret = mbedtls_asn1_get_tag(&inner_p, p + seq_len, &len, |
| 155 | MBEDTLS_ASN1_CONSTRUCTED | |
| 156 | MBEDTLS_ASN1_SET); |
| 157 | if (ret) |
| 158 | return ret; |
| 159 | |
| 160 | ret = mbedtls_asn1_get_tag(&inner_p, p + seq_len, &len, |
| 161 | MBEDTLS_ASN1_OCTET_STRING); |
| 162 | if (ret) |
| 163 | return ret; |
| 164 | |
| 165 | sinfo->msgdigest = inner_p; |
| 166 | sinfo->msgdigest_len = len; |
| 167 | |
| 168 | if (__test_and_set_bit(sinfo_has_message_digest, &sinfo->aa_set)) |
| 169 | return -EINVAL; |
| 170 | } else if (!MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS9_SIGNINGTIME, inner_p, |
| 171 | len)) { |
| 172 | mbedtls_x509_time st; |
| 173 | |
| 174 | inner_p += len; |
| 175 | ret = mbedtls_asn1_get_tag(&inner_p, p + seq_len, &len, |
| 176 | MBEDTLS_ASN1_CONSTRUCTED | |
| 177 | MBEDTLS_ASN1_SET); |
| 178 | if (ret) |
| 179 | return ret; |
| 180 | |
| 181 | ret = mbedtls_x509_get_time(&inner_p, p + seq_len, &st); |
| 182 | if (ret) |
| 183 | return ret; |
| 184 | sinfo->signing_time = x509_get_timestamp(&st); |
| 185 | |
| 186 | if (__test_and_set_bit(sinfo_has_signing_time, &sinfo->aa_set)) |
| 187 | return -EINVAL; |
| 188 | } else if (!MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS9_SMIMECAP, inner_p, |
| 189 | len)) { |
| 190 | if (__test_and_set_bit(sinfo_has_smime_caps, &sinfo->aa_set)) |
| 191 | return -EINVAL; |
| 192 | |
| 193 | if (msg->data_type != OID_msIndirectData && |
| 194 | msg->data_type != OID_data) |
| 195 | return -EINVAL; |
| 196 | } else if (!MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_MICROSOFT_SPOPUSINFO, inner_p, |
| 197 | len)) { |
| 198 | if (__test_and_set_bit(sinfo_has_ms_opus_info, &sinfo->aa_set)) |
| 199 | return -EINVAL; |
| 200 | } else if (!MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_MICROSOFT_STATETYPE, inner_p, |
| 201 | len)) { |
| 202 | if (__test_and_set_bit(sinfo_has_ms_statement_type, &sinfo->aa_set)) |
| 203 | return -EINVAL; |
| 204 | } |
| 205 | |
| 206 | p += seq_len; |
| 207 | } |
| 208 | |
| 209 | if (ret && ret != MBEDTLS_ERR_ASN1_OUT_OF_DATA) |
| 210 | return ret; |
| 211 | |
| 212 | msg->have_authattrs = true; |
| 213 | |
| 214 | /* |
| 215 | * Skip the leading tag byte (MBEDTLS_ASN1_CONTEXT_SPECIFIC | |
| 216 | * MBEDTLS_ASN1_CONSTRUCTED) to satisfy pkcs7_digest() when calculating |
| 217 | * the digest of authattrs. |
| 218 | */ |
| 219 | sinfo->authattrs = aa + 1; |
| 220 | sinfo->authattrs_len = aa_len - 1; |
| 221 | |
| 222 | return 0; |
| 223 | } |
| 224 | |
| 225 | static int x509_populate_content_data(struct pkcs7_message *msg, |
| 226 | mbedtls_pkcs7 *pkcs7_ctx) |
| 227 | { |
| 228 | struct pkcs7_mbedtls_ctx *mctx; |
| 229 | |
| 230 | if (!pkcs7_ctx->content_data.data || |
| 231 | !pkcs7_ctx->content_data.data_len) |
| 232 | return 0; |
| 233 | |
| 234 | mctx = kzalloc(sizeof(*mctx), GFP_KERNEL); |
| 235 | if (!mctx) |
| 236 | return -ENOMEM; |
| 237 | |
| 238 | mctx->content_data = kmemdup(pkcs7_ctx->content_data.data, |
| 239 | pkcs7_ctx->content_data.data_len, |
| 240 | GFP_KERNEL); |
| 241 | if (!mctx->content_data) { |
| 242 | pkcs7_free_mbedtls_ctx(mctx); |
| 243 | return -ENOMEM; |
| 244 | } |
| 245 | |
| 246 | msg->data = mctx->content_data; |
| 247 | msg->data_len = pkcs7_ctx->content_data.data_len; |
| 248 | msg->data_hdrlen = pkcs7_ctx->content_data.data_hdrlen; |
| 249 | msg->data_type = pkcs7_ctx->content_data.data_type; |
| 250 | |
| 251 | msg->mbedtls_ctx = mctx; |
| 252 | return 0; |
| 253 | } |
| 254 | |
| 255 | static int x509_populate_sinfo(struct pkcs7_message *msg, |
| 256 | mbedtls_pkcs7_signer_info *mb_sinfo, |
| 257 | struct pkcs7_signed_info **sinfo) |
| 258 | { |
| 259 | struct pkcs7_signed_info *signed_info; |
| 260 | struct public_key_signature *s; |
| 261 | mbedtls_md_type_t md_alg; |
| 262 | struct pkcs7_sinfo_mbedtls_ctx *mctx; |
| 263 | int ret; |
| 264 | |
| 265 | signed_info = kzalloc(sizeof(*signed_info), GFP_KERNEL); |
| 266 | if (!signed_info) |
| 267 | return -ENOMEM; |
| 268 | |
| 269 | s = kzalloc(sizeof(*s), GFP_KERNEL); |
| 270 | if (!s) { |
| 271 | ret = -ENOMEM; |
| 272 | goto out_no_sig; |
| 273 | } |
| 274 | |
| 275 | mctx = kzalloc(sizeof(*mctx), GFP_KERNEL); |
| 276 | if (!mctx) { |
| 277 | ret = -ENOMEM; |
| 278 | goto out_no_mctx; |
| 279 | } |
| 280 | |
| 281 | /* |
| 282 | * Hash algorithm: |
| 283 | * |
| 284 | * alg_identifier = digestAlgorithm (DigestAlgorithmIdentifier) |
| 285 | * MbedTLS internally checks this field to ensure |
| 286 | * it is the same as digest_alg_identifiers. |
| 287 | * sig_alg_identifier = digestEncryptionAlgorithm |
| 288 | * (DigestEncryptionAlgorithmIdentifier) |
| 289 | * MbedTLS just saves this field without any actions. |
| 290 | * See function pkcs7_get_signer_info() for reference. |
| 291 | * |
| 292 | * Public key algorithm: |
| 293 | * No information related to public key algorithm under MbedTLS signer |
| 294 | * info. Assume that we are using RSA. |
| 295 | */ |
| 296 | ret = mbedtls_oid_get_md_alg(&mb_sinfo->alg_identifier, &md_alg); |
| 297 | if (ret) |
| 298 | goto out_err_sinfo; |
| 299 | s->pkey_algo = "rsa"; |
| 300 | |
| 301 | /* Translate the hash algorithm */ |
| 302 | switch (md_alg) { |
| 303 | case MBEDTLS_MD_SHA1: |
| 304 | s->hash_algo = "sha1"; |
| 305 | s->digest_size = SHA1_SUM_LEN; |
| 306 | break; |
| 307 | case MBEDTLS_MD_SHA256: |
| 308 | s->hash_algo = "sha256"; |
| 309 | s->digest_size = SHA256_SUM_LEN; |
| 310 | break; |
| 311 | case MBEDTLS_MD_SHA384: |
| 312 | s->hash_algo = "sha384"; |
| 313 | s->digest_size = SHA384_SUM_LEN; |
| 314 | break; |
| 315 | case MBEDTLS_MD_SHA512: |
| 316 | s->hash_algo = "sha512"; |
| 317 | s->digest_size = SHA512_SUM_LEN; |
| 318 | break; |
| 319 | /* Unsupported algo */ |
| 320 | case MBEDTLS_MD_MD5: |
| 321 | case MBEDTLS_MD_SHA224: |
| 322 | default: |
| 323 | ret = -EINVAL; |
| 324 | goto out_err_sinfo; |
| 325 | } |
| 326 | |
| 327 | /* |
| 328 | * auth_ids holds AuthorityKeyIdentifier, aka akid |
| 329 | * auth_ids[0]: |
| 330 | * [PKCS#7 or CMS ver 1] - generated from "Issuer + Serial number" |
| 331 | * [CMS ver 3] - generated from skid (subjectKeyId) |
| 332 | * auth_ids[1]: generated from skid (subjectKeyId) |
| 333 | * |
| 334 | * Assume that we are using PKCS#7 (msg->version=1), |
| 335 | * not CMS ver 3 (msg->version=3). |
| 336 | */ |
| 337 | s->auth_ids[0] = asymmetric_key_generate_id(mb_sinfo->serial.p, |
| 338 | mb_sinfo->serial.len, |
| 339 | mb_sinfo->issuer_raw.p, |
| 340 | mb_sinfo->issuer_raw.len); |
| 341 | if (!s->auth_ids[0]) { |
| 342 | ret = -ENOMEM; |
| 343 | goto out_err_sinfo; |
| 344 | } |
| 345 | |
| 346 | /* skip s->auth_ids[1], no subjectKeyId in MbedTLS signer info ctx */ |
| 347 | |
| 348 | /* |
| 349 | * Encoding can be pkcs1 or raw, but only pkcs1 is supported. |
| 350 | * Set the encoding explicitly to pkcs1. |
| 351 | */ |
| 352 | s->encoding = "pkcs1"; |
| 353 | |
| 354 | /* Copy the signature data */ |
| 355 | s->s = kmemdup(mb_sinfo->sig.p, mb_sinfo->sig.len, GFP_KERNEL); |
| 356 | if (!s->s) { |
| 357 | ret = -ENOMEM; |
| 358 | goto out_err_sinfo; |
| 359 | } |
| 360 | s->s_size = mb_sinfo->sig.len; |
| 361 | signed_info->sig = s; |
| 362 | |
| 363 | /* Save the Authenticate Attributes data if exists */ |
| 364 | if (!mb_sinfo->authattrs.data || !mb_sinfo->authattrs.data_len) |
| 365 | goto no_authattrs; |
| 366 | |
| 367 | mctx->authattrs_data = kmemdup(mb_sinfo->authattrs.data, |
| 368 | mb_sinfo->authattrs.data_len, |
| 369 | GFP_KERNEL); |
| 370 | if (!mctx->authattrs_data) { |
| 371 | ret = -ENOMEM; |
| 372 | goto out_err_sinfo; |
| 373 | } |
| 374 | signed_info->mbedtls_ctx = mctx; |
| 375 | |
| 376 | /* If authattrs exists, decode it and parse msgdigest from it */ |
| 377 | ret = authattrs_parse(msg, mctx->authattrs_data, |
| 378 | mb_sinfo->authattrs.data_len, |
| 379 | signed_info); |
| 380 | if (ret) |
| 381 | goto out_err_sinfo; |
| 382 | |
| 383 | no_authattrs: |
| 384 | *sinfo = signed_info; |
| 385 | return 0; |
| 386 | |
| 387 | out_err_sinfo: |
| 388 | pkcs7_free_sinfo_mbedtls_ctx(mctx); |
| 389 | out_no_mctx: |
| 390 | public_key_signature_free(s); |
| 391 | out_no_sig: |
| 392 | kfree(signed_info); |
| 393 | return ret; |
| 394 | } |
| 395 | |
| 396 | /* |
| 397 | * Free a signed information block. |
| 398 | */ |
| 399 | static void pkcs7_free_signed_info(struct pkcs7_signed_info *sinfo) |
| 400 | { |
| 401 | if (sinfo) { |
| 402 | public_key_signature_free(sinfo->sig); |
| 403 | pkcs7_free_sinfo_mbedtls_ctx(sinfo->mbedtls_ctx); |
| 404 | kfree(sinfo); |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | /** |
| 409 | * pkcs7_free_message - Free a PKCS#7 message |
| 410 | * @pkcs7: The PKCS#7 message to free |
| 411 | */ |
| 412 | void pkcs7_free_message(struct pkcs7_message *pkcs7) |
| 413 | { |
| 414 | struct x509_certificate *cert; |
| 415 | struct pkcs7_signed_info *sinfo; |
| 416 | |
| 417 | if (pkcs7) { |
| 418 | while (pkcs7->certs) { |
| 419 | cert = pkcs7->certs; |
| 420 | pkcs7->certs = cert->next; |
| 421 | x509_free_certificate(cert); |
| 422 | } |
| 423 | while (pkcs7->crl) { |
| 424 | cert = pkcs7->crl; |
| 425 | pkcs7->crl = cert->next; |
| 426 | x509_free_certificate(cert); |
| 427 | } |
| 428 | while (pkcs7->signed_infos) { |
| 429 | sinfo = pkcs7->signed_infos; |
| 430 | pkcs7->signed_infos = sinfo->next; |
| 431 | pkcs7_free_signed_info(sinfo); |
| 432 | } |
| 433 | pkcs7_free_mbedtls_ctx(pkcs7->mbedtls_ctx); |
| 434 | kfree(pkcs7); |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | struct pkcs7_message *pkcs7_parse_message(const void *data, size_t datalen) |
| 439 | { |
| 440 | int i; |
| 441 | int ret; |
| 442 | mbedtls_pkcs7 pkcs7_ctx; |
| 443 | mbedtls_pkcs7_signer_info *mb_sinfos; |
| 444 | mbedtls_x509_crt *mb_certs; |
| 445 | struct pkcs7_message *msg; |
| 446 | struct x509_certificate **cert; |
| 447 | struct pkcs7_signed_info **sinfos; |
| 448 | |
| 449 | msg = kzalloc(sizeof(*msg), GFP_KERNEL); |
| 450 | if (!msg) { |
| 451 | ret = -ENOMEM; |
| 452 | goto out_no_msg; |
| 453 | } |
| 454 | |
| 455 | /* Parse the DER encoded PKCS#7 message using MbedTLS */ |
| 456 | mbedtls_pkcs7_init(&pkcs7_ctx); |
| 457 | ret = mbedtls_pkcs7_parse_der(&pkcs7_ctx, data, datalen); |
| 458 | /* Check if it is a PKCS#7 message with signed data */ |
| 459 | if (ret != MBEDTLS_PKCS7_SIGNED_DATA) |
| 460 | goto parse_fail; |
| 461 | |
| 462 | /* Assume that we are using PKCS#7, not CMS ver 3 */ |
| 463 | msg->version = 1; /* 1 for [PKCS#7 or CMS ver 1] */ |
| 464 | |
| 465 | /* Populate the certs to msg->certs */ |
| 466 | for (i = 0, cert = &msg->certs, mb_certs = &pkcs7_ctx.signed_data.certs; |
| 467 | i < pkcs7_ctx.signed_data.no_of_certs && mb_certs; |
| 468 | i++, cert = &(*cert)->next, mb_certs = mb_certs->next) { |
| 469 | ret = x509_populate_cert(mb_certs, cert); |
| 470 | if (ret) |
| 471 | goto parse_fail; |
| 472 | |
| 473 | (*cert)->index = i + 1; |
| 474 | } |
| 475 | |
| 476 | /* |
| 477 | * Skip populating crl, that is not currently in-use. |
| 478 | */ |
| 479 | |
| 480 | /* Populate content data */ |
| 481 | ret = x509_populate_content_data(msg, &pkcs7_ctx); |
| 482 | if (ret) |
| 483 | goto parse_fail; |
| 484 | |
| 485 | /* Populate signed info to msg->signed_infos */ |
| 486 | for (i = 0, sinfos = &msg->signed_infos, |
| 487 | mb_sinfos = &pkcs7_ctx.signed_data.signers; |
| 488 | i < pkcs7_ctx.signed_data.no_of_signers && mb_sinfos; |
| 489 | i++, sinfos = &(*sinfos)->next, mb_sinfos = mb_sinfos->next) { |
| 490 | ret = x509_populate_sinfo(msg, mb_sinfos, sinfos); |
| 491 | if (ret) |
| 492 | goto parse_fail; |
| 493 | |
| 494 | (*sinfos)->index = i + 1; |
| 495 | } |
| 496 | |
| 497 | mbedtls_pkcs7_free(&pkcs7_ctx); |
| 498 | return msg; |
| 499 | |
| 500 | parse_fail: |
| 501 | mbedtls_pkcs7_free(&pkcs7_ctx); |
| 502 | pkcs7_free_message(msg); |
| 503 | out_no_msg: |
| 504 | msg = ERR_PTR(ret); |
| 505 | return msg; |
| 506 | } |