Different behavior between native and K/N

I wrote the code that uses the openssl library to generate a key pair and then import the public key into a file. I couldn’t figure out the problem and wrote equivalent code in C. To my surprise, the native code worked as expected.
Now I can’t understand what the difference is and how to fix the K/N code.
Environment: Ubuntu 24.10, OpenSSL 3.3.1.
Sample project structure:

build.gradle.kts

plugins {
    kotlin("multiplatform") version "2.1.0"
}

repositories {
    mavenCentral()
}

kotlin {
    linuxX64 {
        val main by compilations.getting
        val openssl by main.cinterops.creating
        binaries {
            executable()
        }
    }
}

src/nativeInterop/cinterop/openssl.def

headers = openssl/evp.h openssl/encoder.h
headerFilter = openssl/**

compilerOpts.linux = -I/usr/include
compilerOpts.linux_x64 = -I/usr/include/x86_64-linux-gnu

linkerOpts.linux = -lcrypto
linkerOpts.linux_x64 = -L/usr/lib/x86_64-linux-gnu

src/commonMain/kotlin/Main.kt

import kotlinx.cinterop.*
import openssl.*

private const val EC_CURVE = "secp521r1"
private const val FORMAT = "PEM"
private const val PATH = "/tmp/cert.pem"

@OptIn(ExperimentalForeignApi::class)
fun main() {
    EVP_PKEY_Q_keygen(null, null, "EC", EC_CURVE)
        ?.let { pkey: CPointer<EVP_PKEY> ->
            OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_PUBLIC_KEY, FORMAT, null, null)
                ?.let { context: CPointer<OSSL_ENCODER_CTX> ->
                    BIO_new_file(PATH, "w")
                        ?.let { bio: CPointer<BIO> ->
                            val result = OSSL_ENCODER_to_bio(context, bio)
                            println("RESULT: $result")

                            BIO_free(bio)
                        } ?: println("bio is null")

                    OSSL_ENCODER_CTX_free(context)
                } ?: println("context is null")

            EVP_PKEY_free(pkey)
        } ?: println("pkey is null")
}

The program’s output should be “RESULT: 1” because OSSL_ENCODER_to_bio() returns 1 on success. But “RESULT: 0” is printed, also empty file is produced.

The plain C program

main.c

#include <openssl/evp.h>
#include <openssl/encoder.h>

const char *EC_CURVE = "secp521r1";
const char *FORMAT = "PEM";
const char *PATH = "/tmp/cert.pem";

int main() {
    EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", EC_CURVE);
    if (pkey != NULL) {
        OSSL_ENCODER_CTX *context = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_PUBLIC_KEY, FORMAT, NULL, NULL);
        if (context != NULL) {
            BIO *bio = BIO_new_file(PATH, "w");
            if (bio != NULL) {
                int result = OSSL_ENCODER_to_bio(context, bio);
                printf("RESULT: %d\n", result);
                BIO_free(bio);
            }
            OSSL_ENCODER_CTX_free(context);
        }
        EVP_PKEY_free(pkey);
    }
}
> gcc main.c -lcrypto
> ./a.out
RESULT: 1

and keyfile is created successfully.

I’ve created the ticket in the Kotlin issues tracker and received a response. The problem is on OpenSSL side - one of the passed references is cached but by the time of use it becomes invalid.