7
7
anyhow:: { anyhow, Context , Result } ,
8
8
clap:: ArgMatches ,
9
9
object:: {
10
- elf:: { FileHeader32 , FileHeader64 } ,
10
+ elf:: {
11
+ FileHeader32 , FileHeader64 , ET_DYN , ET_EXEC , STB_GLOBAL , STB_WEAK , STV_DEFAULT ,
12
+ STV_HIDDEN ,
13
+ } ,
11
14
macho:: { MachHeader32 , MachHeader64 } ,
12
15
read:: {
13
16
elf:: { Dyn , FileHeader , SectionHeader , Sym } ,
@@ -440,6 +443,75 @@ const MACHO_ALLOWED_WEAK_SYMBOLS_38_NON_AARCH64: &[&str] = &[
440
443
"_statvfs" ,
441
444
] ;
442
445
446
+ /// Symbols defined in dependency packages.
447
+ ///
448
+ /// We use this list to spot test behavior of symbols belonging to dependency packages.
449
+ /// The list is obviously not complete.
450
+ const DEPENDENCY_PACKAGE_SYMBOLS : & [ & str ] = & [
451
+ // libX11
452
+ "XClearWindow" ,
453
+ "XFlush" ,
454
+ // OpenSSL
455
+ "BIO_ADDR_new" ,
456
+ "BN_new" ,
457
+ "DH_new" ,
458
+ "SSL_extension_supported" ,
459
+ "SSL_read" ,
460
+ "CRYPTO_memcmp" ,
461
+ "ecp_nistz256_neg" ,
462
+ "OPENSSL_instrument_bus" ,
463
+ "x25519_fe64_add" ,
464
+ // libdb
465
+ "__txn_begin" ,
466
+ // libedit / readline
467
+ "rl_prompt" ,
468
+ "readline" ,
469
+ "current_history" ,
470
+ "history_expand" ,
471
+ // libffi
472
+ "ffi_call" ,
473
+ "ffi_type_void" ,
474
+ // ncurses
475
+ "new_field" ,
476
+ "set_field_term" ,
477
+ "set_menu_init" ,
478
+ "winstr" ,
479
+ // gdbm
480
+ "gdbm_close" ,
481
+ "gdbm_import" ,
482
+ // sqlite3
483
+ "sqlite3_initialize" ,
484
+ "sqlite3_close" ,
485
+ // libxcb
486
+ "xcb_create_window" ,
487
+ "xcb_get_property" ,
488
+ // libz
489
+ "deflateEnd" ,
490
+ "gzclose" ,
491
+ "inflate" ,
492
+ // tix
493
+ "Tix_DItemCreate" ,
494
+ "Tix_GrFormat" ,
495
+ // liblzma
496
+ "lzma_index_init" ,
497
+ "lzma_stream_encoder" ,
498
+ // tcl
499
+ "Tcl_Alloc" ,
500
+ "Tcl_ChannelName" ,
501
+ "Tcl_CreateInterp" ,
502
+ // tk
503
+ "TkBindInit" ,
504
+ "TkCreateFrame" ,
505
+ "Tk_FreeGC" ,
506
+ ] ;
507
+
508
+ const PYTHON_EXPORTED_SYMBOLS : & [ & str ] = & [
509
+ "Py_Initialize" ,
510
+ "PyList_New" ,
511
+ // From limited API.
512
+ "Py_CompileString" ,
513
+ ] ;
514
+
443
515
static WANTED_WINDOWS_STATIC_PATHS : Lazy < BTreeSet < PathBuf > > = Lazy :: new ( || {
444
516
[
445
517
PathBuf :: from ( "python/build/lib/libffi.lib" ) ,
@@ -478,6 +550,9 @@ pub struct ValidationContext {
478
550
/// Dynamic libraries required to be loaded.
479
551
pub seen_dylibs : BTreeSet < String > ,
480
552
553
+ /// Symbols exported from dynamic libpython library.
554
+ pub libpython_exported_symbols : BTreeSet < String > ,
555
+
481
556
/// Undefined Mach-O symbols that are required / non-weak.
482
557
pub macho_undefined_symbols_strong : RequiredSymbols ,
483
558
@@ -490,6 +565,8 @@ impl ValidationContext {
490
565
pub fn merge ( & mut self , other : Self ) {
491
566
self . errors . extend ( other. errors ) ;
492
567
self . seen_dylibs . extend ( other. seen_dylibs ) ;
568
+ self . libpython_exported_symbols
569
+ . extend ( other. libpython_exported_symbols ) ;
493
570
self . macho_undefined_symbols_strong
494
571
. merge ( other. macho_undefined_symbols_strong ) ;
495
572
self . macho_undefined_symbols_weak
@@ -699,6 +776,38 @@ fn validate_elf<'data, Elf: FileHeader<Endian = Endianness>>(
699
776
}
700
777
}
701
778
}
779
+
780
+ // Ensure specific symbols in dynamic binaries have proper visibility.
781
+ if matches ! ( elf. e_type( endian) , ET_EXEC | ET_DYN ) {
782
+ // Python 3.8 exports ffi symbols for legacy reasons.
783
+ let is_exception = name == "ffi_type_void" && python_major_minor == "3.8" ;
784
+
785
+ // Non-local symbols belonging to dependencies should have hidden visibility
786
+ // to prevent them from being exported.
787
+ if DEPENDENCY_PACKAGE_SYMBOLS . contains ( & name. as_ref ( ) )
788
+ && matches ! ( symbol. st_bind( ) , STB_GLOBAL | STB_WEAK )
789
+ && symbol. st_visibility ( ) != STV_HIDDEN
790
+ && !is_exception
791
+ {
792
+ context. errors . push ( format ! (
793
+ "{} contains non-hidden dependency symbol {}" ,
794
+ path. display( ) ,
795
+ name
796
+ ) ) ;
797
+ }
798
+
799
+ if let Some ( filename) = path. file_name ( ) {
800
+ let filename = filename. to_string_lossy ( ) ;
801
+
802
+ if filename. starts_with ( "libpython" ) && filename. ends_with ( ".so.1.0" ) {
803
+ if matches ! ( symbol. st_bind( ) , STB_GLOBAL | STB_WEAK )
804
+ && symbol. st_visibility ( ) == STV_DEFAULT
805
+ {
806
+ context. libpython_exported_symbols . insert ( name. to_string ( ) ) ;
807
+ }
808
+ }
809
+ }
810
+ }
702
811
}
703
812
}
704
813
}
@@ -1121,7 +1230,7 @@ fn validate_distribution(
1121
1230
}
1122
1231
}
1123
1232
1124
- // We've now read the contents of the archive. Move on to analyizing the results.
1233
+ // We've now read the contents of the archive. Move on to analyzing the results.
1125
1234
1126
1235
for path in seen_symlink_targets {
1127
1236
if !seen_paths. contains ( & path) {
@@ -1160,6 +1269,21 @@ fn validate_distribution(
1160
1269
}
1161
1270
}
1162
1271
1272
+ // If we've collected symbols exported from libpython, ensure the Python symbols are
1273
+ // in the set.
1274
+ if !context. libpython_exported_symbols . is_empty ( ) {
1275
+ for symbol in PYTHON_EXPORTED_SYMBOLS {
1276
+ if !context
1277
+ . libpython_exported_symbols
1278
+ . contains ( & symbol. to_string ( ) )
1279
+ {
1280
+ context
1281
+ . errors
1282
+ . push ( format ! ( "libpython does not export {}" , symbol) ) ;
1283
+ }
1284
+ }
1285
+ }
1286
+
1163
1287
// On Apple Python 3.8 we need to ban most weak symbol references because 3.8 doesn't have
1164
1288
// the proper runtime guards in place to prevent them from being resolved at runtime,
1165
1289
// which would lead to a crash. See
0 commit comments