Skip to content

Harden blueman/Functions.py: bug fixes, portability, argv launch, tests - #3317

Open
geraldo-netto wants to merge 11 commits into
blueman-project:mainfrom
geraldo-netto:fix/functions-py-hardening
Open

Harden blueman/Functions.py: bug fixes, portability, argv launch, tests#3317
geraldo-netto wants to merge 11 commits into
blueman-project:mainfrom
geraldo-netto:fix/functions-py-hardening

Conversation

@geraldo-netto

Copy link
Copy Markdown
Contributor

Works through the open blueman/Functions.py findings. Each commit is one fix; all changes ship with focused tests in a new test/test_functions.py.

Fixes

  • format_bytes: exact powers of 1024 fell through to the GB branch (format_bytes(1024) returned (9.5e-07, "GB")). Corrected the unit boundaries.
  • parse_os_release: extracted to module scope and parse with str.partition("=") so quoted values containing = (e.g. PRETTY_NAME="Name=Variant") survive instead of being dropped.
  • have(): delegated to shutil.which (honours the executable bit; the old os.EX_OK check only tested existence) and append the sbin dirs only when missing.
  • create_logger: degrade to stderr when /dev/log is unavailable instead of crashing at logger setup (non-Linux / minimal containers).
  • --loglevel: added case-insensitive choices + help, so typos like --loglevel verbose are rejected instead of silently coercing to WARNING.
  • check_bluetooth_status: route messages through logging instead of print.
  • set_proc_title: no-op on non-Linux and guard the libc/prctl access.
  • launch(): added an argv contract (args=[...], each token shell-quoted via GLib.shell_quote) so options can no longer cross argument boundaries; migrated the Notes.py send-note call site. Legacy string form still works (deprecated).
  • Documented create_logger/create_parser.

Tests

  • New test/test_functions.py (45 tests) covering all of the above, including boundary/fuzz inputs for format_bytes, adapter_path_to_name, and parse_os_release.
  • mypy -p blueman --strict clean; flake8 (core codes) clean.

🤖 Generated with Claude Code

geraldo-netto and others added 10 commits June 19, 2026 19:57
Exact powers of 1024 fell through to the GB branch because both band
edges used strict `<`, so format_bytes(1024) returned (9.5e-07, "GB")
instead of (1.0, "KB"); 1 MiB and 1 GiB were mislabelled the same way.

Drop the lower-bound comparison and rely on the cascading upper bounds
so each boundary lands in its own unit. Add test/test_functions.py
covering the 1024/1048576/1073741824 boundaries plus zero, sub-KB,
mid-band, and a huge value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
adapter_path_to_name parses a D-Bus object path with a greedy
`re.search(r".*(hci[0-9]*)", path)` and had no tests. Add cases pinning
the current contract: normal paths, None/empty -> None, no-hci -> None,
case sensitivity, trailing device segments, zero-digit "hci", greedy
last-occurrence selection, and embedded matches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…zz-5, data-3)

parse_os_release was nested inside log_system_info and split each line
with `line.split("=")`, so a valid quoted value containing "=" (e.g.
PRETTY_NAME="Name=Variant") raised ValueError and was dropped from the
logged system info.

Promote it to module scope, parse with str.partition("=") so only the
first "=" separates key from value, and skip blank lines explicitly.
Add tests for basic keys, a value containing "=", unquoted values,
comment/blank lines, lines without "=", and a missing file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
have() hand-rolled a PATH scan with a hardcoded ":/sbin:/usr/sbin"
suffix and checked os.access(path, os.EX_OK) -- os.EX_OK is 0, i.e.
F_OK, so it only confirmed existence, not executability.

Delegate the lookup to shutil.which, which honours the executable bit,
and append the sbin directories to the search path only when they are
not already present. Add tests covering found/not-found, sbin-dir
augmentation, and de-duplication.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
create_logger unconditionally constructed SysLogHandler(address="/dev/log"),
which raises on platforms and minimal containers without that socket,
taking down the whole process at logger setup.

Guard the handler construction and, on OSError, log a warning and keep
the basicConfig stderr handler instead. Add tests for the available,
unavailable, and syslog-disabled paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
--loglevel had no choices and no help, so a typo like `--loglevel
verbose` silently coerced to WARNING across all entry points, leaving
users with quieter logs than intended and no error.

Add case-insensitive choices (debug/info/warning/error/critical) via
type=str.lower plus help text, so argparse rejects unknown values
clearly. Existing consumers compare args.LEVEL.upper(), which is
unaffected. Add tests for default, lowercasing, rejection, help/choices
metadata, the syslog flag, and disabling loglevel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "applet needs to be running" and "Failed to enable bluetooth"
messages went to stdout via print(), bypassing the logging
configuration and leaving no record in syslog/journald.

Route both through logging: logging.exception on the DBusProxyFailed
path (captures the traceback) and logging.error on the enable-failure
path. Add tests for the missing-applet exit path and the no-PowerManager
early return.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
set_proc_title unconditionally loaded libc.so.6 and called
prctl(PR_SET_NAME), both Linux/glibc specific. On other platforms the
LoadLibrary or prctl lookup raises and crashes process startup.

Return early as a no-op on non-Linux, wrap the libc/prctl access in
try/except returning -1 on failure, and document the behaviour in the
docstring. Add tests for the non-Linux no-op, the Linux prctl path, and
the libc-unavailable failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
launch() accepted only a full command-line string, so callers embedded
options directly in cmd (e.g. Notes.py built
"blueman-sendto --delete --device={addr}"), making argument boundaries
depend on GLib command-line parsing rather than an argv contract.

Add an optional args iterable: when provided, the program token and each
argument are shell-quoted individually via GLib.shell_quote, so spaces,
quotes, and shell metacharacters can never cross argument boundaries.
The legacy string form still works when args is omitted (deprecated).
Migrate the Notes.py send-note call site to the argv form.

Add tests for the legacy form, argv quoting, metacharacter
neutralization, the launch result, and path-to-GFile conversion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
doc-1 framed these as dead helpers to "document or remove", but they are
live: every entry point in apps/*.in imports and calls them (the audit
missed the .in sources). Document them instead of removing, noting their
role and the syslog fallback / shared CLI surface. set_proc_title was
already documented alongside the leg-5 platform guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@geraldo-netto
geraldo-netto force-pushed the fix/functions-py-hardening branch from e67b01e to be6a846 Compare June 19, 2026 18:02
Inside an except block logging.exception() is the idiomatic call: it logs
at ERROR with the active traceback, so the explicit exc_info=True is
redundant and the bare logging.error drops the trace. Convert the five
in-handler calls (set_proc_title, _netmask_for_ifacename,
get_local_interfaces x2, parse_os_release). The remaining logging.error
calls are plain error conditions outside any except block, where
logging.exception would log a spurious "NoneType: None", so they are left
as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant