xref: /aosp_15_r20/art/libnativeloader/native_loader.cpp (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "nativeloader"
18 
19 #include "nativeloader/native_loader.h"
20 
21 #include <dlfcn.h>
22 #include <sys/types.h>
23 
24 #include <algorithm>
25 #include <memory>
26 #include <mutex>
27 #include <optional>
28 #include <regex>
29 #include <string>
30 #include <vector>
31 
32 #include "android-base/file.h"
33 #include "android-base/macros.h"
34 #include <android-base/properties.h>
35 #include "android-base/strings.h"
36 #include "android-base/thread_annotations.h"
37 #include "base/macros.h"
38 #include "nativebridge/native_bridge.h"
39 #include "nativehelper/scoped_utf_chars.h"
40 #include "public_libraries.h"
41 
42 #ifdef ART_TARGET_ANDROID
43 #include "android-modules-utils/sdk_level.h"
44 #include "android/api-level.h"
45 #include "library_namespaces.h"
46 #include "log/log.h"
47 #include "nativeloader/dlext_namespaces.h"
48 #endif
49 
50 namespace android {
51 
52 namespace {
53 
54 #if defined(ART_TARGET_ANDROID)
55 
56 using ::android::base::Result;
57 using ::android::nativeloader::LibraryNamespaces;
58 
59 // NATIVELOADER_DEFAULT_NAMESPACE_LIBS is an environment variable that can be
60 // used to list extra libraries (separated by ":") that libnativeloader will
61 // load from the default namespace. The libraries must be listed without paths,
62 // and then LD_LIBRARY_PATH is typically set to the directories to load them
63 // from. The libraries will be available in all classloader namespaces, and also
64 // in the fallback namespace used when no classloader is given.
65 //
66 // kNativeloaderExtraLibs is the name of that fallback namespace.
67 //
68 // NATIVELOADER_DEFAULT_NAMESPACE_LIBS is intended to be used for testing only,
69 // and in particular in the ART run tests that are executed through dalvikvm in
70 // the APEX. In that case the default namespace links to the ART namespace
71 // (com_android_art) for all libraries, which means this can be used to load
72 // test libraries that depend on ART internal libraries.
73 constexpr const char* kNativeloaderExtraLibs = "nativeloader-extra-libs";
74 
75 std::mutex g_namespaces_mutex;
76 LibraryNamespaces* g_namespaces GUARDED_BY(g_namespaces_mutex) = new LibraryNamespaces;
77 NativeLoaderNamespace* g_nativeloader_extra_libs_namespace GUARDED_BY(g_namespaces_mutex) = nullptr;
78 
FindApexNamespace(const char * caller_location)79 std::optional<NativeLoaderNamespace> FindApexNamespace(const char* caller_location) {
80   std::optional<std::string> name = nativeloader::FindApexNamespaceName(caller_location);
81   if (name.has_value()) {
82     // Native Bridge is never used for APEXes.
83     Result<NativeLoaderNamespace> ns =
84         NativeLoaderNamespace::GetExportedNamespace(name.value(), /*is_bridged=*/false);
85     LOG_ALWAYS_FATAL_IF(!ns.ok(),
86                         "Error finding ns %s for APEX location %s: %s",
87                         name.value().c_str(),
88                         caller_location,
89                         ns.error().message().c_str());
90     return ns.value();
91   }
92   return std::nullopt;
93 }
94 
GetNamespaceForApiDomain(nativeloader::ApiDomain api_domain,bool is_bridged)95 Result<NativeLoaderNamespace> GetNamespaceForApiDomain(nativeloader::ApiDomain api_domain,
96                                                        bool is_bridged) {
97   switch (api_domain) {
98     case nativeloader::API_DOMAIN_VENDOR:
99       return NativeLoaderNamespace::GetExportedNamespace(nativeloader::kVendorNamespaceName,
100                                                          is_bridged);
101     case nativeloader::API_DOMAIN_PRODUCT:
102       return NativeLoaderNamespace::GetExportedNamespace(nativeloader::kProductNamespaceName,
103                                                          is_bridged);
104     case nativeloader::API_DOMAIN_SYSTEM:
105       return NativeLoaderNamespace::GetSystemNamespace(is_bridged);
106     default:
107       LOG_FATAL("Invalid API domain %d", api_domain);
108       UNREACHABLE();
109   }
110 }
111 
CreateNativeloaderDefaultNamespaceLibsLink(NativeLoaderNamespace & ns)112 Result<void> CreateNativeloaderDefaultNamespaceLibsLink(NativeLoaderNamespace& ns)
113     REQUIRES(g_namespaces_mutex) {
114   const char* links = getenv("NATIVELOADER_DEFAULT_NAMESPACE_LIBS");
115   if (links == nullptr || *links == 0) {
116     return {};
117   }
118   // Pass nullptr to Link() to create a link to the default namespace without
119   // requiring it to be visible.
120   return ns.Link(nullptr, links);
121 }
122 
GetNativeloaderExtraLibsNamespace()123 Result<NativeLoaderNamespace*> GetNativeloaderExtraLibsNamespace() REQUIRES(g_namespaces_mutex) {
124   if (g_nativeloader_extra_libs_namespace != nullptr) {
125     return g_nativeloader_extra_libs_namespace;
126   }
127 
128   Result<NativeLoaderNamespace> ns =
129       NativeLoaderNamespace::Create(kNativeloaderExtraLibs,
130                                     /*search_paths=*/"",
131                                     /*permitted_paths=*/"",
132                                     /*parent=*/nullptr,
133                                     /*is_shared=*/false,
134                                     /*is_exempt_list_enabled=*/false,
135                                     /*also_used_as_anonymous=*/false);
136   if (!ns.ok()) {
137     return ns.error();
138   }
139   g_nativeloader_extra_libs_namespace = new NativeLoaderNamespace(std::move(ns.value()));
140   Result<void> linked =
141       CreateNativeloaderDefaultNamespaceLibsLink(*g_nativeloader_extra_libs_namespace);
142   if (!linked.ok()) {
143     return linked.error();
144   }
145   return g_nativeloader_extra_libs_namespace;
146 }
147 
148 // If the given path matches a library in NATIVELOADER_DEFAULT_NAMESPACE_LIBS
149 // then load it in the nativeloader-extra-libs namespace, otherwise return
150 // nullptr without error.
TryLoadNativeloaderExtraLib(const char * path)151 Result<void*> TryLoadNativeloaderExtraLib(const char* path) {
152   const char* links = getenv("NATIVELOADER_DEFAULT_NAMESPACE_LIBS");
153   if (links == nullptr || *links == 0) {
154     return nullptr;
155   }
156   std::vector<std::string> lib_list = base::Split(links, ":");
157   if (std::find(lib_list.begin(), lib_list.end(), path) == lib_list.end()) {
158     return nullptr;
159   }
160 
161   std::lock_guard<std::mutex> guard(g_namespaces_mutex);
162   Result<NativeLoaderNamespace*> ns = GetNativeloaderExtraLibsNamespace();
163   if (!ns.ok()) {
164     return ns.error();
165   }
166 
167   Result<void*> res = ns.value()->Load(path);
168   ALOGD("Load %s using ns %s from NATIVELOADER_DEFAULT_NAMESPACE_LIBS match: %s",
169         path,
170         ns.value()->name().c_str(),
171         res.ok() ? "ok" : res.error().message().c_str());
172   return res;
173 }
174 
CreateClassLoaderNamespaceLocked(JNIEnv * env,int32_t target_sdk_version,jobject class_loader,nativeloader::ApiDomain api_domain,bool is_shared,const std::string & dex_path,jstring library_path_j,jstring permitted_path_j,jstring uses_library_list_j)175 Result<NativeLoaderNamespace*> CreateClassLoaderNamespaceLocked(JNIEnv* env,
176                                                                 int32_t target_sdk_version,
177                                                                 jobject class_loader,
178                                                                 nativeloader::ApiDomain api_domain,
179                                                                 bool is_shared,
180                                                                 const std::string& dex_path,
181                                                                 jstring library_path_j,
182                                                                 jstring permitted_path_j,
183                                                                 jstring uses_library_list_j)
184     REQUIRES(g_namespaces_mutex) {
185   Result<NativeLoaderNamespace*> ns = g_namespaces->Create(env,
186                                                            target_sdk_version,
187                                                            class_loader,
188                                                            api_domain,
189                                                            is_shared,
190                                                            dex_path,
191                                                            library_path_j,
192                                                            permitted_path_j,
193                                                            uses_library_list_j);
194   if (!ns.ok()) {
195     return ns;
196   }
197   Result<void> linked = CreateNativeloaderDefaultNamespaceLibsLink(*ns.value());
198   if (!linked.ok()) {
199     return linked.error();
200   }
201   return ns;
202 }
203 
204 #endif  // ART_TARGET_ANDROID
205 
206 }  // namespace
207 
InitializeNativeLoader()208 void InitializeNativeLoader() {
209 #if defined(ART_TARGET_ANDROID)
210   std::lock_guard<std::mutex> guard(g_namespaces_mutex);
211   g_namespaces->Initialize();
212 #endif
213 }
214 
ResetNativeLoader()215 void ResetNativeLoader() {
216 #if defined(ART_TARGET_ANDROID)
217   std::lock_guard<std::mutex> guard(g_namespaces_mutex);
218   g_namespaces->Reset();
219   delete g_nativeloader_extra_libs_namespace;
220   g_nativeloader_extra_libs_namespace = nullptr;
221 #endif
222 }
223 
224 // dex_path_j may be a ':'-separated list of paths, e.g. when creating a shared
225 // library loader - cf. mCodePaths in android.content.pm.SharedLibraryInfo.
CreateClassLoaderNamespace(JNIEnv * env,int32_t target_sdk_version,jobject class_loader,bool is_shared,jstring dex_path_j,jstring library_path_j,jstring permitted_path_j,jstring uses_library_list_j)226 jstring CreateClassLoaderNamespace(JNIEnv* env,
227                                    int32_t target_sdk_version,
228                                    jobject class_loader,
229                                    bool is_shared,
230                                    jstring dex_path_j,
231                                    jstring library_path_j,
232                                    jstring permitted_path_j,
233                                    jstring uses_library_list_j) {
234 #if defined(ART_TARGET_ANDROID)
235   std::string dex_path;
236   if (dex_path_j != nullptr) {
237     ScopedUtfChars dex_path_chars(env, dex_path_j);
238     dex_path = dex_path_chars.c_str();
239   }
240 
241   Result<nativeloader::ApiDomain> api_domain = nativeloader::GetApiDomainFromPathList(dex_path);
242   if (!api_domain.ok()) {
243     return env->NewStringUTF(api_domain.error().message().c_str());
244   }
245 
246   std::lock_guard<std::mutex> guard(g_namespaces_mutex);
247   Result<NativeLoaderNamespace*> ns = CreateClassLoaderNamespaceLocked(env,
248                                                                        target_sdk_version,
249                                                                        class_loader,
250                                                                        api_domain.value(),
251                                                                        is_shared,
252                                                                        dex_path,
253                                                                        library_path_j,
254                                                                        permitted_path_j,
255                                                                        uses_library_list_j);
256   if (!ns.ok()) {
257     return env->NewStringUTF(ns.error().message().c_str());
258   }
259 
260 #else
261   UNUSED(env,
262          target_sdk_version,
263          class_loader,
264          is_shared,
265          dex_path_j,
266          library_path_j,
267          permitted_path_j,
268          uses_library_list_j);
269 #endif
270 
271   return nullptr;
272 }
273 
274 #if defined(ART_TARGET_ANDROID)
ShouldBypassLoadingForB349878424()275 static bool ShouldBypassLoadingForB349878424() {
276   struct stat st;
277   if (stat("/system/lib64/libsobridge.so", &st) != 0 &&
278       stat("/system/lib64/libwalkstack.so", &st) != 0) {
279     return false;
280   }
281   std::string property = android::base::GetProperty("ro.product.build.fingerprint", "");
282   return android_get_device_api_level() == 33 &&
283       (property.starts_with("Xiaomi") ||
284        property.starts_with("Redmi") ||
285        property.starts_with("POCO"));
286 }
287 #endif
288 
OpenNativeLibrary(JNIEnv * env,int32_t target_sdk_version,const char * path,jobject class_loader,const char * caller_location,jstring library_path_j,bool * needs_native_bridge,char ** error_msg)289 void* OpenNativeLibrary(JNIEnv* env,
290                         int32_t target_sdk_version,
291                         const char* path,
292                         jobject class_loader,
293                         const char* caller_location,
294                         jstring library_path_j,
295                         bool* needs_native_bridge,
296                         char** error_msg) {
297 #if defined(ART_TARGET_ANDROID)
298   if (class_loader == nullptr) {
299     // class_loader is null only for the boot class loader (see
300     // IsBootClassLoader call in JavaVMExt::LoadNativeLibrary), i.e. the caller
301     // is in the boot classpath.
302     *needs_native_bridge = false;
303     if (caller_location != nullptr) {
304       std::optional<NativeLoaderNamespace> ns = FindApexNamespace(caller_location);
305       if (ns.has_value()) {
306         const android_dlextinfo dlextinfo = {
307             .flags = ANDROID_DLEXT_USE_NAMESPACE,
308             .library_namespace = ns.value().ToRawAndroidNamespace(),
309         };
310         void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
311         char* dlerror_msg = handle == nullptr ? strdup(dlerror()) : nullptr;
312         ALOGD("Load %s using APEX ns %s for caller %s: %s",
313               path,
314               ns.value().name().c_str(),
315               caller_location,
316               dlerror_msg == nullptr ? "ok" : dlerror_msg);
317         if (dlerror_msg != nullptr) {
318           *error_msg = dlerror_msg;
319         }
320         return handle;
321       }
322     }
323 
324     // Check if the library is in NATIVELOADER_DEFAULT_NAMESPACE_LIBS and should
325     // be loaded from the kNativeloaderExtraLibs namespace.
326     {
327       Result<void*> handle = TryLoadNativeloaderExtraLib(path);
328       if (!handle.ok()) {
329         *error_msg = strdup(handle.error().message().c_str());
330         return nullptr;
331       }
332       if (handle.value() != nullptr) {
333         return handle.value();
334       }
335     }
336 
337     // Handle issue b/349878424.
338     static bool bypass_loading_for_b349878424 = ShouldBypassLoadingForB349878424();
339 
340     if (bypass_loading_for_b349878424 &&
341         (strcmp("libsobridge.so", path) == 0 || strcmp("libwalkstack.so", path) == 0)) {
342       // Load a different library to pretend the loading was successful. This
343       // allows the device to boot.
344       ALOGD("Loading libbase.so instead of %s due to b/349878424", path);
345       path = "libbase.so";
346     }
347 
348     // Fall back to the system namespace. This happens for preloaded JNI
349     // libraries in the zygote.
350     void* handle = OpenSystemLibrary(path, RTLD_NOW);
351     char* dlerror_msg = handle == nullptr ? strdup(dlerror()) : nullptr;
352     ALOGD("Load %s using system ns (caller=%s): %s",
353           path,
354           caller_location == nullptr ? "<unknown>" : caller_location,
355           dlerror_msg == nullptr ? "ok" : dlerror_msg);
356     if (dlerror_msg != nullptr) {
357       *error_msg = dlerror_msg;
358     }
359     return handle;
360   }
361 
362   // If the caller is in any of the system image partitions and the library is
363   // in the same partition then load it without regards to public library
364   // restrictions. This is only done if the library is specified by an absolute
365   // path, so we don't affect the lookup process for libraries specified by name
366   // only.
367   if (caller_location != nullptr &&
368       // Apps in the partition may have their own native libraries which should
369       // be loaded with the app's classloader namespace, so only do this for
370       // libraries in the partition-wide lib(64) directories.
371       nativeloader::IsPartitionNativeLibPath(path) &&
372       // Don't do this if the system image is older than V, to avoid any compat
373       // issues with apps and shared libs in them.
374       android::modules::sdklevel::IsAtLeastV()) {
375     nativeloader::ApiDomain caller_api_domain = nativeloader::GetApiDomainFromPath(caller_location);
376     if (caller_api_domain != nativeloader::API_DOMAIN_DEFAULT) {
377       nativeloader::ApiDomain library_api_domain = nativeloader::GetApiDomainFromPath(path);
378 
379       if (library_api_domain == caller_api_domain) {
380         bool is_bridged = false;
381         if (library_path_j != nullptr) {
382           ScopedUtfChars library_path_utf_chars(env, library_path_j);
383           if (library_path_utf_chars[0] != '\0') {
384             is_bridged = NativeBridgeIsPathSupported(library_path_utf_chars.c_str());
385           }
386         }
387 
388         Result<NativeLoaderNamespace> ns = GetNamespaceForApiDomain(caller_api_domain, is_bridged);
389         if (!ns.ok()) {
390           ALOGD("Failed to find ns for caller %s in API domain %d to load %s (is_bridged=%b): %s",
391                 caller_location,
392                 caller_api_domain,
393                 path,
394                 is_bridged,
395                 ns.error().message().c_str());
396           *error_msg = strdup(ns.error().message().c_str());
397           return nullptr;
398         }
399 
400         *needs_native_bridge = ns.value().IsBridged();
401         Result<void*> handle = ns.value().Load(path);
402         ALOGD("Load %s using ns %s for caller %s in same partition (is_bridged=%b): %s",
403               path,
404               ns.value().name().c_str(),
405               caller_location,
406               is_bridged,
407               handle.ok() ? "ok" : handle.error().message().c_str());
408         if (!handle.ok()) {
409           *error_msg = strdup(handle.error().message().c_str());
410           return nullptr;
411         }
412         return handle.value();
413       }
414     }
415   }
416 
417   NativeLoaderNamespace* ns;
418   const char* ns_descr;
419   {
420     std::lock_guard<std::mutex> guard(g_namespaces_mutex);
421 
422     ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader);
423     ns_descr = "class loader";
424 
425     if (ns == nullptr) {
426       // This is the case where the classloader was not created by ApplicationLoaders
427       // In this case we create an isolated not-shared namespace for it.
428       const std::string empty_dex_path;
429       Result<NativeLoaderNamespace*> res =
430           CreateClassLoaderNamespaceLocked(env,
431                                            target_sdk_version,
432                                            class_loader,
433                                            nativeloader::API_DOMAIN_DEFAULT,
434                                            /*is_shared=*/false,
435                                            empty_dex_path,
436                                            library_path_j,
437                                            /*permitted_path_j=*/nullptr,
438                                            /*uses_library_list_j=*/nullptr);
439       if (!res.ok()) {
440         ALOGD("Failed to create isolated ns for %s (caller=%s)",
441               path,
442               caller_location == nullptr ? "<unknown>" : caller_location);
443         *error_msg = strdup(res.error().message().c_str());
444         return nullptr;
445       }
446       ns = res.value();
447       ns_descr = "isolated";
448     }
449   }
450 
451   *needs_native_bridge = ns->IsBridged();
452   Result<void*> handle = ns->Load(path);
453   ALOGD("Load %s using %s ns %s (caller=%s): %s",
454         path,
455         ns_descr,
456         ns->name().c_str(),
457         caller_location == nullptr ? "<unknown>" : caller_location,
458         handle.ok() ? "ok" : handle.error().message().c_str());
459   if (!handle.ok()) {
460     *error_msg = strdup(handle.error().message().c_str());
461     return nullptr;
462   }
463   return handle.value();
464 
465 #else   // !ART_TARGET_ANDROID
466   UNUSED(env, target_sdk_version, class_loader, caller_location);
467 
468   // Do some best effort to emulate library-path support. It will not
469   // work for dependencies.
470   //
471   // Note: null has a special meaning and must be preserved.
472   std::string library_path;  // Empty string by default.
473   if (library_path_j != nullptr && path != nullptr && path[0] != '/') {
474     ScopedUtfChars library_path_utf_chars(env, library_path_j);
475     library_path = library_path_utf_chars.c_str();
476   }
477 
478   std::vector<std::string> library_paths = base::Split(library_path, ":");
479 
480   for (const std::string& lib_path : library_paths) {
481     *needs_native_bridge = false;
482     const char* path_arg;
483     std::string complete_path;
484     if (path == nullptr) {
485       // Preserve null.
486       path_arg = nullptr;
487     } else {
488       complete_path = lib_path;
489       if (!complete_path.empty()) {
490         complete_path.append("/");
491       }
492       complete_path.append(path);
493       path_arg = complete_path.c_str();
494     }
495     void* handle = dlopen(path_arg, RTLD_NOW);
496     if (handle != nullptr) {
497       return handle;
498     }
499     if (NativeBridgeIsSupported(path_arg)) {
500       *needs_native_bridge = true;
501       handle = NativeBridgeLoadLibrary(path_arg, RTLD_NOW);
502       if (handle != nullptr) {
503         return handle;
504       }
505       *error_msg = strdup(NativeBridgeGetError());
506     } else {
507       *error_msg = strdup(dlerror());
508     }
509   }
510   return nullptr;
511 #endif  // !ART_TARGET_ANDROID
512 }
513 
CloseNativeLibrary(void * handle,const bool needs_native_bridge,char ** error_msg)514 bool CloseNativeLibrary(void* handle, const bool needs_native_bridge, char** error_msg) {
515   bool success;
516   if (needs_native_bridge) {
517     success = (NativeBridgeUnloadLibrary(handle) == 0);
518     if (!success) {
519       *error_msg = strdup(NativeBridgeGetError());
520     }
521   } else {
522     success = (dlclose(handle) == 0);
523     if (!success) {
524       *error_msg = strdup(dlerror());
525     }
526   }
527 
528   return success;
529 }
530 
NativeLoaderFreeErrorMessage(char * msg)531 void NativeLoaderFreeErrorMessage(char* msg) {
532   // The error messages get allocated through strdup, so we must call free on them.
533   free(msg);
534 }
535 
536 #if defined(ART_TARGET_ANDROID)
OpenNativeLibraryInNamespace(NativeLoaderNamespace * ns,const char * path,bool * needs_native_bridge,char ** error_msg)537 void* OpenNativeLibraryInNamespace(NativeLoaderNamespace* ns, const char* path,
538                                    bool* needs_native_bridge, char** error_msg) {
539   Result<void*> handle = ns->Load(path);
540   if (!handle.ok() && error_msg != nullptr) {
541     *error_msg = strdup(handle.error().message().c_str());
542   }
543   if (needs_native_bridge != nullptr) {
544     *needs_native_bridge = ns->IsBridged();
545   }
546   return handle.ok() ? *handle : nullptr;
547 }
548 
IsNamespaceNativeBridged(const struct NativeLoaderNamespace * ns)549 bool IsNamespaceNativeBridged(const struct NativeLoaderNamespace* ns) { return ns->IsBridged(); }
550 
551 // native_bridge_namespaces are not supported for callers of this function.
552 // This function will return nullptr in the case when application is running
553 // on native bridge.
FindNamespaceByClassLoader(JNIEnv * env,jobject class_loader)554 android_namespace_t* FindNamespaceByClassLoader(JNIEnv* env, jobject class_loader) {
555   std::lock_guard<std::mutex> guard(g_namespaces_mutex);
556   NativeLoaderNamespace* ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader);
557   if (ns != nullptr && !ns->IsBridged()) {
558     return ns->ToRawAndroidNamespace();
559   }
560   return nullptr;
561 }
562 
FindNativeLoaderNamespaceByClassLoader(JNIEnv * env,jobject class_loader)563 NativeLoaderNamespace* FindNativeLoaderNamespaceByClassLoader(JNIEnv* env, jobject class_loader) {
564   std::lock_guard<std::mutex> guard(g_namespaces_mutex);
565   return g_namespaces->FindNamespaceByClassLoader(env, class_loader);
566 }
567 
LinkNativeLoaderNamespaceToExportedNamespaceLibrary(struct NativeLoaderNamespace * ns,const char * exported_ns_name,const char * library_name,char ** error_msg)568 void LinkNativeLoaderNamespaceToExportedNamespaceLibrary(struct NativeLoaderNamespace* ns,
569                                                          const char* exported_ns_name,
570                                                          const char* library_name,
571                                                          char** error_msg) {
572   Result<NativeLoaderNamespace> exported_ns =
573       NativeLoaderNamespace::GetExportedNamespace(exported_ns_name, ns->IsBridged());
574   if (!exported_ns.ok()) {
575     *error_msg = strdup(exported_ns.error().message().c_str());
576     return;
577   }
578 
579   Result<void> linked = ns->Link(&exported_ns.value(), std::string(library_name));
580   if (!linked.ok()) {
581     *error_msg = strdup(linked.error().message().c_str());
582   }
583 }
584 
585 #endif  // ART_TARGET_ANDROID
586 
587 }  // namespace android
588