1
0
mirror of https://github.com/systemd/systemd synced 2026-03-30 11:44:49 +02:00

Compare commits

..

7 Commits

Author SHA1 Message Date
Martin Hundebøll
c6c43d677a cryptsetup: fix wrong argument order for mechanism vs. name
Fixes: https://github.com/systemd/systemd/issues/39655
2025-11-19 16:07:51 +01:00
Zbigniew Jędrzejewski-Szmek
6f1f3a3917
Deduplicate and improve messages from ssh-generator (#39785) 2025-11-19 16:00:34 +01:00
Daan De Meyer
3f0fc93219 tools: Add script to detect unused symbols in libshared
Symbols exported by libshared can't get pruned by the linker, so
every unused exported symbol is effectively dead code we ship to users
for no good reason. Let's add a script to analyze how many such symbols
we have.

We also add a meson test to run the script on all of our binaries.
Since it detects unused symbols and still has a few false positives,
don't enable the test by default similar to the clang-tidy tests.

The script was 100% vibe coded by Github Copilot with Claude Sonnet 4.5
as the model.

Current results are (without the unused symbols list):

Analysis of libsystemd-shared-259.so
======================================================================
Total exported symbols: 4830
  (excluding public API symbols starting with 'sd_')
Used symbols: 4672
Unused symbols: 158
Usage rate: 96.7%
2025-11-19 13:14:15 +01:00
Daan De Meyer
4186aad374 libudev: Don't pull in libshared_static
- Move devices-nodes.c to src/basic as it's super trivial anyway
- Duplicate udev_queue_is_empty() in libudev-util.c as it's trivial
  anyway.
2025-11-19 13:14:15 +01:00
Zbigniew Jędrzejewski-Szmek
8c3acba63b ssh-generator: suppress error message for vsock EADDRNOTAVAIL
In logs in the Fedora OpenQA CI:
Nov 17 22:20:06 fedora systemd-ssh-generator[4117]: Failed to query local AF_VSOCK CID: Cannot assign requested address
Nov 17 22:20:06 fedora (generato[4088]: /usr/lib/systemd/system-generators/systemd-ssh-generator failed with exit status 1.
Nov 17 22:20:06 fedora systemd[1]: sshd-vsock.socket: Unit configuration changed while unit was running, and no socket file descriptors are open. Unit not functional until restarted.

AF_VSOCK is not configured there and systemd-ssh-generator should just exit
quietly. vsock_get_local_cid() already does some logging at debug level, so we
don't need to.

There is also a second bug, we report modifications to the unit have just
created. I think we have an issue open for this somewhere, but cannot find it.
2025-11-19 11:37:49 +01:00
Zbigniew Jędrzejewski-Szmek
8c019224a1 ssh-generator: split out one more helper function 2025-11-19 11:37:48 +01:00
Zbigniew Jędrzejewski-Szmek
7e8fe8e29f ssh-generator: split out common helper function 2025-11-19 11:37:48 +01:00
13 changed files with 393 additions and 56 deletions

View File

@ -15,7 +15,7 @@ project('systemd', 'c',
add_test_setup(
'default',
exclude_suites : ['clang-tidy', 'integration-tests'],
exclude_suites : ['clang-tidy', 'unused-symbols', 'integration-tests'],
is_default : true,
)
@ -2227,7 +2227,7 @@ libudev = shared_library(
implicit_include_directories : false,
link_args : ['-shared',
'-Wl,--version-script=' + libudev_sym_path],
link_with : [libsystemd_static, libshared_static],
link_with : [libsystemd_static],
link_whole : libudev_basic,
dependencies : [threads,
userspace],
@ -3000,6 +3000,19 @@ if meson.version().version_compare('>=1.4.0')
endforeach
endif
symbol_analysis_exes = []
foreach name, exe : executables_by_name
symbol_analysis_exes += exe
endforeach
find_unused_library_symbols = find_program('tools/find-unused-library-symbols.py')
test(
'libshared-unused-symbols',
find_unused_library_symbols,
suite : 'unused-symbols',
args : [libshared, libcore] + nss_targets + pam_targets + symbol_analysis_exes,
)
run_target(
'check-api-docs',
depends : [man, libsystemd, libudev],

View File

@ -1,7 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "shared-forward.h"
#include "basic-forward.h"
int encode_devnode_name(const char *str, char *str_enc, size_t len);
int allow_listed_char_for_devnode(char c, const char *additional);

View File

@ -23,6 +23,7 @@ basic_sources = files(
'compress.c',
'conf-files.c',
'confidential-virt.c',
'device-nodes.c',
'devnum-util.c',
'dirent-util.c',
'dlfcn-util.c',

View File

@ -1233,7 +1233,7 @@ static int measured_crypt_activate_by_passphrase(
if (keyslot < 0)
return keyslot;
return measured_crypt_activate_by_volume_key(cd, mechanism, name, keyslot, vk, vks, flags);
return measured_crypt_activate_by_volume_key(cd, name, mechanism, keyslot, vk, vks, flags);
shortcut:
keyslot = crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags);

View File

@ -12,7 +12,6 @@
#include "errno-util.h"
#include "fd-util.h"
#include "io-util.h"
#include "udev-util.h"
/**
* SECTION:libudev-queue
@ -64,6 +63,11 @@ static struct udev_queue* udev_queue_free(struct udev_queue *udev_queue) {
return mfree(udev_queue);
}
static int udev_queue_is_empty(void) {
return access("/run/udev/queue", F_OK) < 0 ?
(errno == ENOENT ? true : -errno) : false;
}
/**
* udev_queue_ref:
* @udev_queue: udev queue context

View File

@ -57,7 +57,6 @@ shared_sources = files(
'daemon-util.c',
'data-fd-util.c',
'dev-setup.c',
'device-nodes.c',
'discover-image.c',
'dissect-image.c',
'dm-util.c',

View File

@ -3,15 +3,26 @@
executables += [
generator_template + {
'name' : 'systemd-ssh-generator',
'sources' : files('ssh-generator.c'),
'sources' : files(
'ssh-generator.c',
'ssh-util.c',
),
'extract' : files(
'ssh-util.c',
),
},
libexec_template + {
'name' : 'systemd-ssh-proxy',
'sources' : files('ssh-proxy.c'),
'sources' : files(
'ssh-proxy.c',
),
},
libexec_template + {
'name' : 'systemd-ssh-issue',
'sources' : files('ssh-issue.c'),
'sources' : files(
'ssh-issue.c',
),
'objects' : ['systemd-ssh-generator'],
},
]

View File

@ -17,6 +17,7 @@
#include "socket-netlink.h"
#include "socket-util.h"
#include "special.h"
#include "ssh-util.h"
#include "string-util.h"
#include "strv.h"
#include "virt.h"
@ -212,29 +213,15 @@ static int add_vsock_socket(
return 0;
}
_cleanup_close_ int vsock_fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (vsock_fd < 0) {
if (ERRNO_IS_NOT_SUPPORTED(errno)) {
log_debug("Not creating AF_VSOCK ssh listener, since AF_VSOCK is not available.");
return 0;
}
return log_error_errno(errno, "Unable to test if AF_VSOCK is available: %m");
}
vsock_fd = safe_close(vsock_fd);
r = vsock_open_or_warn(/* ret= */ NULL);
if (r <= 0)
return r;
/* Determine the local CID so that we can log it to help users to connect to this VM */
unsigned local_cid;
r = vsock_get_local_cid(&local_cid);
if (r < 0) {
if (ERRNO_IS_DEVICE_ABSENT(r)) {
log_debug("Not creating AF_VSOCK ssh listener, since /dev/vsock is not available (even though AF_VSOCK is).");
return 0;
}
return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m");
}
r = vsock_get_local_cid_or_warn(&local_cid);
if (r <= 0)
return r;
r = make_sshd_template_unit(
dest,

View File

@ -15,7 +15,7 @@
#include "mkdir.h"
#include "parse-argument.h"
#include "pretty-print.h"
#include "socket-util.h"
#include "ssh-util.h"
#include "string-util.h"
#include "tmpfile-util.h"
#include "virt.h"
@ -135,33 +135,11 @@ static int acquire_cid(unsigned *ret_cid) {
return 0;
}
_cleanup_close_ int vsock_fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (vsock_fd < 0) {
if (ERRNO_IS_NOT_SUPPORTED(errno)) {
log_debug("Not creating issue file, since AF_VSOCK is not available.");
*ret_cid = 0;
return 0;
}
r = vsock_open_or_warn(/* ret= */ NULL);
if (r <= 0)
return r;
return log_error_errno(errno, "Unable to test if AF_VSOCK is available: %m");
}
vsock_fd = safe_close(vsock_fd);
unsigned local_cid;
r = vsock_get_local_cid(&local_cid);
if (r < 0) {
if (ERRNO_IS_DEVICE_ABSENT(r)) {
log_debug("Not creating issue file, since /dev/vsock is not available (even though AF_VSOCK is).");
*ret_cid = 0;
return 0;
}
return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m");
}
*ret_cid = local_cid;
return 1;
return vsock_get_local_cid_or_warn(ret_cid);
}
static int run(int argc, char* argv[]) {

View File

@ -0,0 +1,40 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/socket.h>
#include <unistd.h>
#include "errno-util.h"
#include "log.h"
#include "socket-util.h"
#include "ssh-util.h"
int vsock_open_or_warn(int *ret) {
int fd = RET_NERRNO(socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0));
if (ERRNO_IS_NEG_NOT_SUPPORTED(fd))
log_debug_errno(fd, "AF_VSOCK is not available, ignoring: %m");
else if (fd < 0)
return log_error_errno(fd, "Unable to test if AF_VSOCK is available: %m");
if (ret)
*ret = fd;
else
close(fd);
return fd >= 0;
}
int vsock_get_local_cid_or_warn(unsigned *ret) {
int r;
r = vsock_get_local_cid(ret);
if (ERRNO_IS_NEG_DEVICE_ABSENT(r) || r == -EADDRNOTAVAIL) {
if (ERRNO_IS_NEG_DEVICE_ABSENT(r))
log_debug_errno(r, "/dev/vsock is not available (even though AF_VSOCK is), ignoring: %m");
if (ret)
*ret = 0; /* bogus value */
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m");
return 1;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
int vsock_open_or_warn(int *ret);
int vsock_get_local_cid_or_warn(unsigned *ret);

View File

@ -0,0 +1,300 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1-or-later
"""
Find unused symbols in a shared library.
This script analyzes a shared library and a list of executables that link
against it to determine which publicly exported symbols from the library
are not used by any of the executables or by the library itself internally.
The script checks for symbol usage in three ways:
1. Internal library references: Uses objdump -R to find relocations within
the library that reference its own exported symbols
2. Executable dependencies: Uses nm to find undefined symbols in executables
that match the library's exported symbols
3. Cross-references: Identifies symbols used across all provided binaries
This comprehensive approach ensures that symbols used internally by the
library are not incorrectly marked as unused.
"""
import argparse
import subprocess
import sys
from pathlib import Path
def get_exported_symbols(library_path):
"""
Extract all exported (public) symbols from a shared library.
Public API symbols (those starting with 'sd_') are excluded from the analysis
since they cannot be removed or made private due to API compatibility requirements.
Returns a set of symbol names that are defined and exported by the library.
"""
try:
result = subprocess.run(
['nm', '--dynamic', '--defined-only', '--extern-only', library_path],
capture_output=True,
text=True,
check=True
)
except subprocess.CalledProcessError as e:
print(f"Error: Failed to run nm on {library_path}: {e}", file=sys.stderr)
sys.exit(1)
except FileNotFoundError:
print("Error: 'nm' command not found. Please install binutils.", file=sys.stderr)
sys.exit(1)
symbols = set()
for line in result.stdout.splitlines():
parts = line.split()
if len(parts) >= 3:
# Format: address type name
symbol_type = parts[1]
symbol_name = parts[2]
# Include text (T) and data (D, B, R) symbols
if symbol_type in ('T', 'D', 'B', 'R', 'W'):
# Strip version information (e.g., @@SD_SHARED or @SD_SHARED)
symbol_name = symbol_name.split('@')[0]
# Skip public API symbols (those starting with sd_)
if symbol_name.startswith('sd_'):
continue
symbols.add(symbol_name)
return symbols
def get_undefined_symbols(executable_path):
"""
Extract all undefined symbols from an executable.
These are symbols that the executable expects to be provided by
shared libraries it links against.
"""
try:
result = subprocess.run(
['nm', '--dynamic', '--undefined-only', executable_path],
capture_output=True,
text=True,
check=True
)
except subprocess.CalledProcessError as e:
print(f"Warning: Failed to run nm on {executable_path}: {e}", file=sys.stderr)
return set()
except FileNotFoundError:
print("Error: 'nm' command not found. Please install binutils.", file=sys.stderr)
sys.exit(1)
symbols = set()
for line in result.stdout.splitlines():
parts = line.split()
if len(parts) >= 2:
# Format: type name (no address for undefined symbols)
symbol_name = parts[1]
# Strip version information (e.g., @SD_SHARED)
symbol_name = symbol_name.split('@')[0]
symbols.add(symbol_name)
return symbols
def verify_executable_links_library(executable_path, library_name):
"""
Verify that an executable actually links against the given library.
Returns True if the executable links against a library with the given name.
"""
try:
result = subprocess.run(
['ldd', executable_path],
capture_output=True,
text=True,
check=True
)
except (subprocess.CalledProcessError, FileNotFoundError):
# If ldd fails or doesn't exist, we'll skip the verification
return True
# Check if library_name appears in the ldd output
for line in result.stdout.splitlines():
if library_name in line:
return True
return False
def get_library_internal_references(library_path, exported_symbols):
"""
Find which exported symbols are referenced internally within the library itself.
This uses objdump to look for relocations that reference the exported symbols.
"""
try:
result = subprocess.run(
['objdump', '-R', library_path],
capture_output=True,
text=True,
check=True
)
except subprocess.CalledProcessError as e:
print(f"Warning: Failed to run objdump on {library_path}: {e}", file=sys.stderr)
return set()
except FileNotFoundError:
print("Warning: 'objdump' command not found. Internal references won't be detected.",
file=sys.stderr)
return set()
internal_refs = set()
for line in result.stdout.splitlines():
parts = line.split()
if len(parts) >= 3:
# objdump -R format: offset type symbol
# The symbol is typically the last field
symbol_name = parts[-1]
# Strip version information
symbol_name = symbol_name.split('@')[0]
# Only include if it's one of our exported symbols
if symbol_name in exported_symbols:
internal_refs.add(symbol_name)
return internal_refs
def find_unused_symbols(library_path, executable_paths, verify_linkage=True):
"""
Find symbols exported by the library that are not used by any executable.
Args:
library_path: Path to the shared library
executable_paths: List of paths to executables
verify_linkage: Whether to verify executables link against the library
Returns:
Tuple of (unused_symbols, exported_symbols, used_symbols)
"""
library_name = Path(library_path).name
# Get all exported symbols from the library (excluding public API symbols)
exported_symbols = get_exported_symbols(library_path)
if not exported_symbols:
print(f"Warning: No exported symbols found in {library_path}", file=sys.stderr)
return set(), set(), set()
# Collect all symbols used by the executables
used_symbols = set()
# First, check if the library references its own exported symbols internally
internal_refs = get_library_internal_references(library_path, exported_symbols)
used_symbols.update(internal_refs)
for exe_path in executable_paths:
# Optionally verify linkage
if verify_linkage and not verify_executable_links_library(exe_path, library_name):
print(f"Warning: {exe_path} does not appear to link against {library_name}",
file=sys.stderr)
undefined_symbols = get_undefined_symbols(exe_path)
# Only count symbols that are actually exported by our library
used_symbols.update(undefined_symbols & exported_symbols)
# Find unused symbols
unused_symbols = exported_symbols - used_symbols
return unused_symbols, exported_symbols, used_symbols
def main():
parser = argparse.ArgumentParser(
description='Find unused exported symbols in a shared library'
)
parser.add_argument(
'library',
help='Path to the shared library to analyze'
)
parser.add_argument(
'executables',
nargs='+',
help='Paths to executables that link against the library'
)
parser.add_argument(
'--no-verify-linkage',
action='store_true',
help='Skip verification that executables actually link against the library'
)
parser.add_argument(
'--show-used',
action='store_true',
help='Also show used symbols'
)
parser.add_argument(
'--stats-only',
action='store_true',
help='Only show statistics, not individual symbols'
)
args = parser.parse_args()
# Verify library exists
library_path = Path(args.library)
if not library_path.exists():
print(f"Error: Library not found: {library_path}", file=sys.stderr)
sys.exit(1)
# Verify executables exist
executable_paths = []
for exe in args.executables:
exe_path = Path(exe)
if not exe_path.exists():
print(f"Warning: Executable not found: {exe_path}", file=sys.stderr)
else:
executable_paths.append(str(exe_path))
if not executable_paths:
print("Error: No valid executables provided", file=sys.stderr)
sys.exit(1)
# Analyze symbols
unused, exported, used = find_unused_symbols(
str(library_path),
executable_paths,
verify_linkage=not args.no_verify_linkage
)
# Print results
print(f"Analysis of {library_path.name}")
print("=" * 70)
print(f"Total exported symbols: {len(exported)}")
print(f" (excluding public API symbols starting with 'sd_')")
print(f"Used symbols: {len(used)}")
print(f"Unused symbols: {len(unused)}")
print(f"Usage rate: {len(used)/len(exported)*100:.1f}%" if exported else "N/A")
print()
if not args.stats_only:
if unused:
print("Unused symbols:")
print("-" * 70)
for symbol in sorted(unused):
print(f" {symbol}")
print()
else:
print("All exported symbols are used!")
print()
if args.show_used and used:
print("Used symbols:")
print("-" * 70)
for symbol in sorted(used):
print(f" {symbol}")
print()
# Exit with non-zero if there are unused symbols (useful for CI)
sys.exit(0 if not unused else 1)
if __name__ == '__main__':
main()