Skip to content

native: OpenHarmony native library fixes - #136194

Open
springmin wants to merge 11 commits into
dotnet:mainfrom
springmin:pr/ohos-native-fixes
Open

springmin wants to merge 11 commits into
dotnet:mainfrom
springmin:pr/ohos-native-fixes

Conversation

@springmin

@springmin springmin commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Make the OpenHarmony native libraries work on device without platform-specific
build plumbing: the close_range fallback for the seccomp sandbox, the full
ethtool speed read, the crypto-library selection via the existing static
OpenSSL link, and the lower-priority shell fallback for a sibling libssl.

Changes

  • src/native/libs/CMakeLists.txt: select System.Security.Cryptography.Native
    for OpenHarmony (the same branch as FORCE_ANDROID_OPENSSL) and skip
    System.Net.Security.Native, which has no gssapi in the OHOS sysroot.
  • System.Native/pal_interfaceaddresses.c: read the full ethtool speed on
    Android/OpenHarmony, whose UAPI headers lack ethtool_cmd_speed.
  • System.Native/pal_process.c: on OpenHarmony, walk /proc/self/fd with
    getdents() and set FD_CLOEXEC in the forked child; close_range is trapped
    by the sandbox with SIGSYS. Also fixes the HAVE_CLOSE_RANGE cmakedefine01
    guard so the __NR_close_range fallback stays available on Linux.
  • System.Security.Cryptography.Native/opensslshim.c: after the normal soname
    probes fail, try libssl next to the shim for shell-launched dotnet exec on
    OpenHarmony (the device has no system libssl.so.3).
  • Five files.

Validation (HarmonyOS device, aarch64, SDK 11.0.100-rc.2.26451.112)

$ dotnet --info
 Version: 11.0.100-rc.2.26451.112
 RID: openharmony-arm64

$ dotnet hello.dll
Hello, World!

$ dotnet probe.dll
hello: runtime=11.0.0 rid=openharmony-arm64
child: exit=0 out=11.0.100-rc.2.26451.112
nic: wlan0 type=Ethernet status=Up speed=-1
... (all interfaces enumerate)
rsa: keysize=2048
tls: ok bytes=577
  • The child line exercises the handle-inheritance fallback; the nic line the
    speed read. The rsa/tls lines exercise the crypto library selected via the
    existing static-link path.
  • The loader fallback is lower precedence: HAP resolves libs/<abi> through the
    normal soname probes, and shell dotnet exec falls back to a sibling only after
    those probes fail.
  • Interface speeds are reported as unknown (-1) on this device's wlan0 (the
    driver does not expose an ethtool speed); enumeration itself succeeds.

Test

  • Create a console app with the OpenHarmony SDK, build it, and run the produced
    dll with the existing host (dotnet app.dll; the apphost cannot be executed
    directly on this device).
  • The commands and output above are from that run.

Out of scope

  • static OpenSSL linking policy / OpenSSL redistribution
  • an OH_Crypto-backed System.Security.Cryptography implementation
  • general CA-store packaging changes

The System.Net.Security.Native exclusion in
Microsoft.NETCore.Native.Unix.targets is in #136185.

Supersedes #135866 and #136187.

Note

This PR was authored with AI assistance (agent tooling) under the author's direction.

- zstd: use the C90 qsort workaround on OpenHarmony.
- System.Net.Security.Native: skip gssapi on OpenHarmony and use the OpenSSL
  crypto shim.
- System.Native: read the full ethtool speed, and avoid close_range (the sandbox
  traps it with SIGSYS) by setting FD_CLOEXEC from /proc/self/fd.
- System.Security.Cryptography.Native: resolve libssl/libcrypto by absolute
  path next to the shim and keep the dlopen shim in the NativeAOT static
  archive.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

springmin added a commit to springmin/runtime-ohos that referenced this pull request Oct 21, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @bartonjs, @vcsjones, @dotnet/area-system-security
See info in area-owners.md if you want to be subscribed.

springmin added a commit to springmin/runtime-ohos that referenced this pull request Oct 21, 2026
Rebased pr/ohos-native-robustness onto d267e98 (b3ddf1c,
11 files 123+/9-) and opened the PR. Runtime in flight: dotnet#136185,
dotnet#136186, dotnet#136194, dotnet#136198; SDK: dotnet#56732, dotnet#56733.
springmin added a commit to springmin/runtime-ohos that referenced this pull request Oct 21, 2026
Reverting the OpenHarmony TFM special casing leaves an empty diff; the
standard TargetOS TFM path works like FreeBSD. Replied to jkotas and
closed the PR. Runtime in flight: dotnet#136185, dotnet#136194, dotnet#136198.
@springmin

Copy link
Copy Markdown
Contributor Author

The native fixes were consolidated at @am11's request. @jkotas noted on #135839 that a consolidated multi-area PR needs sign-offs from owners of multiple areas and will be slow.

#136194 currently touches process, network interfaces, crypto/gssapi and zstd, but has no area-owner reviews yet (and only carries the area-System.Security label). Would you prefer that we:

(a) keep it consolidated and request sign-off from each area owner, or
(b) split it back into per-area PRs (process / interface addresses / crypto+gssapi / zstd) with the same device evidence, keeping the shared build/CMake changes here?

We're happy to do either. If (b), we'll reopen the per-area PRs (or open fresh focused ones) and link the same device validation (hello, child process, NIC, RSA/TLS).

Note

This comment was drafted with AI assistance (agent tooling) under the author's direction.

@am11

am11 commented Oct 21, 2026

Copy link
Copy Markdown
Member

a consolidated multi-area PR needs sign-offs from owners of multiple areas and will be slow.

The "port" PRs in the past had native code clubbed together (src/native, src/coreclr etc.). Those PRs are miscellaneous in nature. Once the initial work is done, we switch to regular per-area work routine. e.g. the code in this PR has less to do with "domain knowledge" and more to do with coding conventions and looking out for unnecessary code bloat.

am11

This comment was marked as resolved.

@am11 am11 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<NetCoreAppNativeLibrary Include="System.Net.Security.Native" Condition="!$(_targetOS.StartsWith('tvos')) and '$(_linuxLibcFlavor)' != 'bionic'" />

Change this line to:

<NetCoreAppNativeLibrary Include="System.Net.Security.Native" Condition="!$(_targetOS.StartsWith('tvos')) and '$(_linuxLibcFlavor)' != 'bionic' and '$(_targetOS)' != 'openharmony'" />

Drafted with AI assistance.

Comment thread src/native/external/zstd.cmake Resolved
Comment thread src/native/libs/CMakeLists.txt Resolved
@springmin

Copy link
Copy Markdown
Contributor Author

Addressed in 146fda08281:

  • Removed the custom OpenSSL static plumbing (the crypto CMakeLists/extra_libs changes, -Wl,--exclude-libs,ALL, and -linkstaticopenssl in the build scripts); the build now relies on -portablebuild=false -staticLibLink / StaticOpenSslLinking.
  • Simplified the /proc/self/fd walker to getdents() + struct dirent, removed the custom struct/raw syscall/rlimit scan, and fixed the HAVE_CLOSE_RANGE #cmakedefine01 guard.
  • Removed the DlOpenCandidate/unreachable/dead shim code; reverted the zstd and ifaddrs workarounds; combined the CMake conditions.
  • The System.Net.Security.Native exclusion in Microsoft.NETCore.Native.Unix.targets is already in OpenHarmony: NativeAOT and pack integration #136185.

Note

This reply was drafted with AI assistance (agent tooling) under the author's direction.

@am11

am11 commented Oct 21, 2026

Copy link
Copy Markdown
Member

You still need am11@7ef728f.

@springmin

Copy link
Copy Markdown
Contributor Author

Applied your DlOpenNextToShim commit in f69b18db2cf; the net diff is now four files (process/ifaddrs/CMakeLists/opensslshim).

Note

This reply was drafted with AI assistance (agent tooling) under the author's direction.

@springmin

Copy link
Copy Markdown
Contributor Author

Gentle ping when you have a moment: CI is green (100/100) and DlOpenNextToShim is applied in f69b18db2cf. No rush — just checking whether anything else is needed before approval.

Note

This comment was drafted with AI assistance (agent tooling) under the author's direction.

@am11
am11 requested review from bartonjs, janvorli and jkotas October 23, 2026 00:29

@am11 am11 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

DlOpen(MAKELIB("4"));
}

#if defined(TARGET_OPENHARMONY)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dotnet/area-system-security, it wasn't put next to android etc. above because the "published app" mode takes linux route and only dotnet exec myapp.dll takes this fallback.


#if HAVE_FDWALK
// Callback used with fdwalk() on Illumos/Solaris to set FD_CLOEXEC on all file descriptors >= 3.
#if defined(TARGET_OPENHARMONY)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be reviewed and signed off by @adamsitnik

{
#ifdef TARGET_ANDROID
nii->Speed = (int64_t)ecmd.speed;
#if defined(TARGET_ANDROID) || defined(TARGET_OPENHARMONY)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be reviewed by @dotnet/ncl .

Also, do we need a regression test for this bug on Android?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks android specific, @simonrozsival could you or someone from your team take a look?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re regression test, I already commented on #136354 that a regression test should be part of the same PR that introduces the fix to make it less chaotic

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regression test is now part of this PR in 6364648. Could you take a look?

@springmin

Copy link
Copy Markdown
Contributor Author

Thanks for the review and sign-off, @am11. Thanks for the pointers, @jkotas.

  • pal_process.c: the change only sets FD_CLOEXEC via the /proc/self/fd walker when close_range is unavailable or trapped; happy to have @adamsitnik take a look.
  • pal_interfaceaddresses.c: for Android this replaces the missing ethtool_cmd_speed() inline helper with the equivalent (speed_hi << 16) | speed, so speeds above 65535 Mbps are no longer truncated. A deterministic regression test would need control over the kernel's ethtool response; the existing NetworkInterface tests don't assert link speed and the Android legs run on emulators with an unstable reported speed. If you'd like a test anyway, could you point me at the preferred location (managed test with a mocked PAL, or a native test)?

Note

This comment was drafted with AI assistance (agent tooling) under the author's direction.

@springmin

Copy link
Copy Markdown
Contributor Author

Local deterministic repro of the Android/OpenHarmony speed truncation (no high-speed NIC needed). Harness and output:
https://gist.github.com/springmin/772d3adc5ccea004c7116a59c04bedb9

It compiles the real pal_interfaceaddresses.c with -DTARGET_ANDROID / -DTARGET_OPENHARMONY and mocks getifaddrs()/ioctl() to report an Ethernet NIC with speed=34464, speed_hi=1 (100000 Mbps):

  • upstream (Android, ecmd.speed): speed=34464000000 (34.464 Gbps)
  • this PR (Android and OpenHarmony): speed=100000000000 (100 Gbps)

So the truncation is real and the fix is correct. A managed test cannot reproduce this deterministically (it needs a NIC reporting >65535 Mbps); I can add a conditional /sys/class/net/<iface>/speed check in NetworkInterfaceBasicTest, and track the deterministic native harness as a follow-up test.

Note

This comment was drafted with AI assistance (agent tooling) under the author's direction.

The Android/OpenHarmony ethtool path can truncate speeds above 65535 Mbps.
Compare NetworkInterface.Speed with /sys/class/net/<iface>/speed when the
kernel reports a high-speed link; skip interfaces where sysfs is unavailable.
@springmin

Copy link
Copy Markdown
Contributor Author

Moved the regression test from #136354 into this PR (6364648b5a4) so the fix and its regression test land together. CI is rerunning.

Note

This comment was drafted with AI assistance (agent tooling) under the author's direction.

@springmin

Copy link
Copy Markdown
Contributor Author

Thanks again for the earlier review and pointers.

The regression test from #136354 is now included in this PR in 6364648b5a4; the current head is fb78e3bb14e.

Following the review comments, would it be possible for the relevant area owners to take a look when convenient?

  • @adamsitnik: the pal_process.c change around the /proc/self/fd walker — would you be able to review and sign off if it looks good?
  • @dotnet/ncl / @simonrozsival: the pal_interfaceaddresses.c speed_hi fix and the regression test — could you or someone on the team take a look?
  • @dotnet/area-system-security: am11 left a note on opensslshim.c:272 about the fallback path; could someone confirm that part?

No rush at all — happy to adjust anything that looks off.

NOTE: AI-assisted.

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@springmin Big thanks for your contribution. PTAL at my comments.

@springmin

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Updated in cc12cc9: RestrictHandleInheritance now tries close_range()/the raw syscall first and only falls back to SetCloexecForOpenFds on OpenHarmony when errno == SIGSYS.

Could you take another look when convenient?

NOTE: AI-assisted.

OpenHarmony seccomp uses SCMP_ACT_TRAP for close_range, so the syscall receives SIGSYS and never returns; errno == SIGSYS cannot be observed. Keep the /proc/self/fd walker on OpenHarmony.
@springmin

Copy link
Copy Markdown
Contributor Author

I verified this on-device. OpenHarmony uses SCMP_ACT_TRAP for close_range, so the syscall never returns; the child is killed by SIGSYS (signal 31). A standalone probe of raw syscall 436 with (3, UINT_MAX, CLOSE_RANGE_CLOEXEC) reproduced it, and libc does not export close_range here. The app seccomp policy also leaves it out of the allowlist. Thus errno == SIGSYS is unreachable; the branch now keeps the compile-time guard and the /proc/self/fd walker, with a comment explaining why. We can revisit if the 7.1 relaxation reaches devices.

NOTE: AI-assisted.

@springmin

Copy link
Copy Markdown
Contributor Author

The current diff is now five files. Most earlier review threads were against pre-rebase revisions and are marked outdated, so I resolved those stale threads. The remaining threads refer to the current code.

On close_range: the on-device probe shows OpenHarmony uses SCMP_ACT_TRAP, so the raw syscall is killed by SIGSYS and never returns; errno == SIGSYS cannot be observed. The current branch keeps the TARGET_OPENHARMONY guard and uses the /proc/self/fd walker. Details: #136194 (comment)

The speed regression test is included in 6364648b5a4. Could @adamsitnik take a final look at pal_process.c, @dotnet/area-system-security at opensslshim.c, and @dotnet/ncl at pal_interfaceaddresses.c? The remaining red jobs are Helix storage 403.

NOTE: AI-assisted.

#if defined(TARGET_OPENHARMONY)
// OpenHarmony has no system libssl.so.3, and a shell-launched process only searches system
// directories, so fall back to the libssl deployed next to this library.
static void DlOpenNextToShim(const char* libraryName)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I certainly hope that the OSSL assets are being pulled in at the last minute via something like a Docker build script, and not being redisted with the concept of the runtime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR does not add or redistribute any OpenSSL binaries. It only adds the fallback loader in opensslshim.c. The OSSL assets are produced at image-build time in the #1786 image; the runtime repository contains no libssl/libcrypto binaries. The sibling lookup does not package an asset — it only tries an already-deployed libssl after the normal system lookups fail.

NOTE: AI-assisted.

DlOpen(MAKELIB("4"));
}

#if defined(TARGET_OPENHARMONY)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this block is supposed to match probe order with Linux, then I wonder if we'd better having a conditional path expansion at the beginning of DlOpen; or something like the TARGET_OPENHARMONY build causes current DlOpen to be named DlOpenCore and the current proposed DlOpenNextToShim takes over the name DlOpen.

That'd be similar to how, when we loaded OpenSSL on OSX/macOS, we handled the .so vs .dylib inside DlOpen.


If they're only coincidentally the same, and it's expected to deviate at some point, then this is fine.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I described it above #136194 (comment), it's not needed for published app (which use linux route) that's why it has lower precedence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sibling lookup is intentionally after the normal probe order: libssl.so.3, 1.1, and 4 are tried first. DlOpenNextToShim only runs when all of those fail, which is the shell-launched dotnet exec case. Published apps take the Linux route and are unaffected.

I can factor the path expansion into DlOpen if you prefer, but it would add a second path-selection mechanism for a fallback that is intentionally lower precedence.

NOTE: AI-assisted.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So on OpenHarmony it's going to try all versions from system paths, then all versions from a local path? Why would the library be findable for a published library but not a local-exec one?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A published app (HAP) is launched by the system with its libs/<abi> directory on the loader search path, so libssl.so.3 resolves by soname. A shell-launched dotnet runs in the default loader namespace, which only searches system directories (/system/lib64, /vendor/lib64, /lib64, ... per ld-musl-namespace-*.ini). The platform's own OpenSSL is libssl_openssl.z.so, so no libssl.so.3 is reachable there, and our native libs have no $ORIGIN runpath to find one next to the shim.

Published apps are the production-facing case, and dotnet app.dll from a shell is the secondary one, hence the precedence: the normal probes run first (so the HAP's bundled copy or LD_LIBRARY_PATH wins), and the sibling lookup is only a last-resort fallback.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, static linking OpenSSL is bad for two reasons. 1) no security updates unless you rebuild, and 2) France (et al).

It's the same as redisting, really (except that you make it harder for people to use you as a passive dependency to the crypto libraries).

But, not really relevant for this PR. Just a thing to share for "if some official OpenHarmony .NET SDK is building with static linking for OpenSSL, that's a problem"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. France (et al).

Pretty sure OpenHarmony is really only intended for China today.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static linking doesn't use this loading mechanism. I'm not sure how @springmin came to the conclusion that reverting this file is the ask here? @bartonjs only wanted to understand the loading order choice. Yes, OHOS is ... quite unique than what we are used to (in a fragmented way..): #132953 (comment).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — I over-interpreted the earlier questions as a request to remove the fallback. I've restored opensslshim.c in 6f6d01e4ad1; the PR is back to the original five-file scope.

The fallback remains the lower-priority path for shell-launched dotnet exec. A HAP resolves the app-provided library through the normal libs/<abi> soname probes; the sibling lookup only runs after those probes fail. I'm not splitting this into a loader PR.

On the static-linking point: this PR does not add or change static OpenSSL linking. The static-linked SDK artifact is pre-existing; I'm not using it as justification for the loader, and static-linking policy remains out of scope for this PR.

The mid/long-term items — static-linking policy, an OH_Crypto-backed System.Security.Cryptography, and general CA-store packaging — will be separate issues/PRs after this lands.

Note

This comment was drafted with AI assistance (agent tooling) under the author's direction.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an isolated fallback check, so the validation now covers both the normal soname path and the lower-priority sibling path:

  • HAP (JIT, app-provided libs/arm64-v8a): OpenSSL_version reports 3.5.9, the loaded libcrypto.so.3 is under the HAP-private libs/arm64 directory, RSA 2048 and TLS (577 bytes) pass. This is the normal soname lookup.
  • Shell dotnet exec: LD_LIBRARY_PATH pointing at 3.5.9 reports 3.5.9; the default environment reports 3.6.5. RSA/TLS pass in both cases.
  • Independent fallback branch: I forced the normal libssl.so.3 probe to fail with a non-ELF placeholder on LD_LIBRARY_PATH and supplied libcrypto.so.3 separately. With libssl.so.3 next to the shim, RSA 2048 and TLS pass. Removing that sibling reproduces No usable version of libssl was found and an RSA failure. The shim itself has only NEEDED libc.so.
  • Version consistency: the reported OpenSSL_version matched the selected library in all runs.

These are validation-only results; no files or code changed, and the five-file scope is unchanged. The HAP and LD_LIBRARY_PATH runs exercise the normal soname probes; the third run exercises DlOpenNextToShim.

Note

This comment was drafted with AI assistance (agent tooling) under the author's direction.

The sibling libssl probe is a separate loader-policy change; keep this PR scoped to the process, interface-address and build-selection fixes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Security community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants