Both decoders treat a fixed-width, space-padded wire field as a NUL-terminated
C string.
In sgDecodeFlightId(), flightId is char[8] inside the packed wire struct,
followed by rsvd[4] and checksum. strcpy() copies until it finds a zero byte,
so a transponder that sends eight non-NUL characters and non-zero trailing
bytes makes it run off the end of the local struct and into the stack, writing
into a nine-byte destination the whole way.
sgDecodeInstall() has the same bug with registration, char[7] copied into an
eight-byte destination.
Copy exactly the field width and terminate. The trailing memset was already
writing the terminator in the right place; it just ran after the overflow.
Both are reachable from the transponder serial link: a FlightID_Response or
Installation_Response is decoded as soon as it arrives.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Julian Oes <julian@oes.ch>
Three out-of-bounds writes reachable from the MSP serial link, all driven by
fields the connected device controls.
MspV1::Receive() read payload_size + MSP_CRC_SIZE bytes into a caller-supplied
buffer without knowing how big it was. payload_size is a uint8_t taken straight
off the wire and the only caller passes a 255-byte stack buffer, so a frame
advertising 255 bytes of payload wrote one attacker-chosen byte past the end.
Pass the destination size and reject a frame that does not fit.
The VTX table handlers indexed 1-based table entries with upper-bound-only
checks. For MSP_SET_VTXTABLE_BAND, band 0 satisfies "band <= BAND_COUNT" and
writes 29 bytes at vtx_bands[-1]. For MSP_SET_VTXTABLE_POWERLEVEL, packet[0] of
0 promotes to -1, which is less than POWER_LEVEL_COUNT, and writes at
power_levels[-1]. Require the index to be at least 1.
Neither handler checked the frame was long enough to contain the struct it
casts the packet to, so also require that.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Julian Oes <julian@oes.ch>
The strict GNSS quality checks were re-armed whenever the vehicle was not
airborne, so a transient accuracy excursion after arming or during the
takeoff transient could tear down GNSS aiding and force a blind land.
Hold the latch while armed and only re-run the strict checks once parked.
Assisted-by: Claude:claude-opus-5
Signed-off-by: bresch <brescianimathieu@gmail.com>
* fix(macos.sh): trust brew taps before tapping them
Recent Homebrew validates every formula of a tap while tapping it. On
Homebrew 6.0+ loading a formula from an untrusted tap is refused, so
`brew tap osx-cross/arm` and `brew tap PX4/px4` now fail with
"Cannot tap ...: invalid syntax in tap!" because `brew trust` only ran
afterwards. Without the PX4/px4 tap, `brew install` aborts on `fastdds`
before pouring any package, ccache included, and the macOS CI job dies
with `ccache: command not found`. main has been red since 2026-09-14.
Run `brew trust` before `brew tap`; it accepts taps that are not
installed yet.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(macos.sh): trust osrf/simulation tap before tapping it
Same ordering problem as the toolchain taps: with Homebrew 6.0+ tapping
an untrusted tap fails, so there is no tap clone to apply the
gz-tap-pin.txt pin to. The later install of
osrf/simulation/gz-harmonic then taps it implicitly at HEAD, silently
skipping the pin and risking Gazebo building from source.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
* fix(macos.sh): set -e to abort on first failure
The script kept going after a failed `brew tap` or `brew install`, so
the macOS CI job only failed much later with `ccache: command not
found`, far away from the actual cause.
Add `set -e`. Commands that are allowed to fail (`brew uninstall flock`
on a machine without it, `brew doctor` warnings, fetching the gz tap pin
whose failure is already handled by the following checkout) get an
explicit `|| true`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Signed-off-by: Julian Oes <julian@oes.ch>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Julian Oes <julian@oes.ch>
num_events is a uint8 while events is a fixed array of five, and the reply
arrives on /fmu/in/arming_check_reply, which uXRCE-DDS exposes as an input
topic. checkAndReport() looped to num_events and memcpy'd into events[i], so a
value above five wrote past the end of the heap-allocated reply.
Clamp on ingestion, next to the existing registration_id validation, so the
stored reply cannot describe more events than it can hold.
Reported by @REYu6.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Julian Oes <julian@oes.ch>
cbat_sub_cb() stored msg.cell_count straight into battery_status and then used
it as the loop bound when copying per-cell voltages. cell_count is a uint8 the
battery node chooses and voltage_cell_v holds fourteen floats, so a node
reporting more than fourteen cells writes past the end of the array: at 255 that
is 241 floats, most of a kilobyte, through the rest of the status structure and
into the next battery instance. The values written come from the node as well.
The BatteryInfoAux handler in the same file already clamps this way; the CBAT
one did not. Use math::min there too, and give both the array size rather than
the literal 14 the other one carried.
Reported by @ttzero25.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Julian Oes <julian@oes.ch>
Once PX4 has latched onto a UDP client address, it keeps sending there
forever. A client that is restarted comes back with a different ephemeral
source port, so PX4 talks into the void and only a reboot brings the link
back. This is easy to hit when connecting straight to PX4's local port
(e.g. 18570) instead of listening on 14550/14540, which is what makes
some setups such as docker easier.
Instead of dropping the latched address on a timer, hand it over only
when someone else actually shows up: every incoming packet either
refreshes the latch, or - if it comes from a different source and the
current client has been quiet for 3 seconds - takes it over. A client
that only listens is therefore left alone, and an address configured
with -t or -c is never replaced.
The UDP read now resets nread before polling. It is declared outside the
loop and only assigned when POLLIN is set, so on a POLLERR wake-up the
stale value would have the previous buffer parsed again. It also lets
the source address comparison rely on nread to know it is valid.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
* ci(ros): reuse the Jazzy toolchain for translation tests
Use the same pinned PX4 development image as the ROS integration tests while preserving the Humble environment and both distro-specific caches.
Assisted-by: Copilot:gpt-6-astra
* ci(ros): pin published Humble and Jazzy development images
Use the multi-architecture toolchains published from d8c01514ac for both translation-test distributions. Reuse bundled ccache while preserving separate caches, message-versioning checks and checkout-matched translation builds.
Assisted-by: Copilot:gpt-6-astra
* feat(ros2): add Humble development images and distro releases
Build Humble on Jammy without replacing its ROS DDS libraries: Agent 2.4.3
links private, pinned Fast DDS/CDR dependencies. Publish Humble and Jazzy
tags independently while leaving packaged runtime images Jazzy-only.
Assisted-by: Copilot:gpt-6-astra
* refactor(ros2): share pinned Agent DDS libraries across distros
Build the Agent against the same private Fast DDS and Fast CDR pins on Humble and Jazzy, without replacing either ROS underlay. Remove distro-specific build branches and name the dependency manifest for its shared role.
Assisted-by: Copilot:gpt-6-astra
* fix(control_allocation): drop control axes that are not independently achievable
Stopping a hexarotor motor and its geometric opposite (CA_FAILURE_MODE=1)
leaves four rotors whose roll and yaw effectiveness rows are collinear -
exactly so on ideal geometry, 1.3 deg apart on a surveyed airframe. Yaw is
no longer independently controllable, but nothing in control allocation
checked for that, so geninv inverted a near-singular matrix and returned a
mix with roll gains ~50x and thrust gains ~2000x with random signs
(Moore-Penrose residual 1e4). Sequential desaturation cannot recover from
gains that large and left ~0.85 on two rotors at zero thrust demand: a
vehicle armed on the ground at idle lifted off by itself.
dropDependentAxes() walks the effectiveness rows in priority order
(thrust z, roll, pitch, thrust x, thrust y, yaw) and zeroes any row whose
component orthogonal to the higher-priority rows falls below
kMinAxisIndependence = 1e-2, i.e. within ~5.7 deg of their span. The axis
becomes explicitly unachievable and is reported through the dropped-axis
mask instead of being inverted into garbage.
The metric is unit-free, so it does not depend on CA_ROTORn_KM or arm
length: verified identical decisions for KM from 0.003 to 0.5 and arms from
0.05 m to 5 m. Across in-tree geometries healthy multirotors score 1.00,
hexa/octo with one motor out 0.75/0.89 and tailsitter 0.70, none affected;
only quad-with-one-motor-out and hexarotor failed+opposite drop yaw.
The orthogonalization runs through the Cholesky factor of the Gram matrix
of the accepted unit rows rather than on the rows themselves, so no
NUM_ACTUATORS-long vectors land on the stack: 288 B of frame instead of
632 B, which matters because this runs on wq:rate_ctrl with 3150 B.
Co-Authored-By: Matthias Grob <maetugr@gmail.com>
* feat(matrix): add dot product for matrix::Slice
* fix(control_allocation): replace custom rowDot() with new matrix::Slice::dot()
---------
Co-authored-by: Matthias Grob <maetugr@gmail.com>
Address the review feedback from @hamishwillee: add a board photo,
mechanical and electrical data, a Radio Control section stating that RC
is wired directly to the FMU, and per-connector pinout tables taken from
the manufacturer documentation.
Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Julian Oes <julian@oes.ch>
The board was added against the previous NuttX release. After rebasing
onto the NuttX 12.12 upgrade the build fails in px4_log.cpp because
stdout now expands to lib_get_stream(), which is only declared with
CONFIG_FILE_STREAM, and that defaults to off under CONFIG_DEFAULT_SMALL.
Apply the same defconfig changes the upgrade made to the other H7
boards: enable FILE_STREAM, switch to the renamed options
(BOARD_CRASHDUMP_CUSTOM, ETC_ROMFS, LINE_MAX, ...) and drop the ones
that no longer exist.
Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Julian Oes <julian@oes.ch>
The NWBlue Pro H757 is a 30x30 mm FPV flight controller built around the
CubePilot CubeNode H757 module: STM32H757, on-module ICM45686 IMU and
on-carrier DPS368 baro on SPI3, IIS2MDC magnetometer on I2C3, microSD on
SDMMC2, 9 DShot/PWM outputs, CAN1 and six UARTs.
Pin assignments follow the ArduPilot NWBLUE_PROH757 hwdef, including board
ID 5730. The sensor rotations do not: the hwdef specifies ROTATION_ROLL_180
for the IMU, but on this hardware both the IMU and the magnetometer are
unrotated, confirmed on the bench and by compass calibration.
PLL1P runs at 480MHz so the timer clock is 240MHz, which DShot600 divides
into exactly 20 ticks per bit. At 400MHz that division truncates and every
bit comes out 4.2% short, which some ESCs reject. Bidirectional DShot works
on every output except FMU_CH6: that one is TIM4_CH4, and the H7 DMAMUX has
no request line for it.
Timers: TIM1/2/3/4 drive the outputs, TIM8_CH3 the buzzer so a passive
piezo produces a real tone, TIM12 the HRT and TIM6 the uavcan clock. TIM5
is deliberately left unallocated - the HRT does not work there (every
periodic work item runs once and is never rescheduled, while
interrupt-driven peripherals keep going so the board still looks alive),
and it is also the H7 default for the uavcan clock, where it kept DroneCAN
from coming up.
There is no analog OSD chip, so an HD VTX has to render the OSD itself over
MSP DisplayPort on the VTX connector (USART6 / TEL2). MSP_OSD_CONFIG is
left unset so that port can equally serve as a plain TEL2.
px4_uploader learns the board's USB ID (CubePilot VID 0x2DAE, PID 0x2001).
bringup.md records what has been verified on hardware and what has not.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Julian Oes <julian@oes.ch>
The Infineon DPS310 and DPS368 are register-compatible; only their
product-ID register value differs (DPS310 = 0x10, DPS368 = 0x11).
Recognise both in the init check so the same driver instance can be
used for either part on boards that fit a DPS368.
Signed-off-by: Julian Oes <julian@oes.ch>
The ListDirectoryWithTime (opcode 16) entry format is
<type><name>\t<size>\t<mtime>\0, but PX4 only applied it to files and
sent directories as a bare D<name>. MAVSDK's server already sends
D<name>\t0\t<mtime>, so the two servers disagreed and a client had to
special-case which one it was talking to.
Directories are now stat()ed for their modification time and reported
with size 0, matching MAVSDK. The plain ListDirectory (opcode 3)
response is unchanged: existing clients take everything after the D as
the directory name there.
Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Julian Oes <julian@oes.ch>
The write authorization added to _workOpen() in #28584 tested the open
flags against O_WRONLY | O_RDWR | O_CREAT | O_TRUNC. On Linux O_RDONLY is
zero so a read-only open never matched, but on NuttX O_RDONLY is a bit and
O_RDWR is O_RDONLY | O_WRONLY, so every OpenFileRO was treated as a write
and confined to the storage directory. That rejected reads of anything
outside /fs/microsd with "File Protected", including the component
metadata PX4 advertises under /etc/extras, so QGC could no longer load it
from NuttX boards while SITL kept working.
Decide on the access mode instead. CreateFile and OpenFileWO both open
with O_WRONLY, so nothing changes for the write paths.
Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Julian Oes <julian@oes.ch>
Make the standalone ROS toolchain available from the same registries as the ROS runtime images. Preserve opt-in publication, existing GHCR tags and both architecture SBOM indexes.
Assisted-by: Copilot
Gazebo ROS image publication exceeds BuildKit 0.32's 40 MiB attestation limit. Pin the builder daemon to 0.33.0 for its 80 MiB limit while preserving the existing scanner and complete package/file coverage.
Assisted-by: Copilot:gpt-6-astra
Build each PR firmware and matching ROS workspace from source against the digest-pinned Jazzy toolchain. Run the interface library unit and SIH integration suites without reusing stale firmware or generated messages.
Assisted-by: Copilot:gpt-6-astra
Provide a supported Jazzy environment for SIH/Gazebo development and source builds, with pinned tooling and reproducible multi-architecture image publishing. Separate package and container assets from ROS source preparation, and keep checkout workspaces fresh through a Python CLI.
Assisted-by: Copilot:gpt-6-astra
MAV_CMD_CONDITION_GATE's SupportedCommandParams entry had mask 0x00,
rejecting any mission item that set param5-7 (lat/lon/alt). But
common.xml defines those as the gate's location, and mission_block.cpp
reads _mission_item.lat/lon to compute the gate-crossing plane -- so
every gate mission item was rejected with MAV_MISSION_INVALID_PARAM5_X
before reaching the navigator at all.
Also allow param1 (Geometry) and param2 (UseAltitude), which are
likewise defined by common.xml but were masked out.
Claude-Session: https://claude.ai/code/session_0196c3ZPtNhnjiExcFPDh7JG
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
We would like to refactor agam-robotics directory to agam for ease of use
and brevity. boards/agam-robotics becomes boards/agam, making the build
target agam_fmu-v6xrt_default. The bundled bootloader is rebuilt under the
new target name.
* feat(gps): log GNSS info from UBX_MSG_MON_SPAN and UBX_MON_RF for improved GNSS debugging capabilities.
Introduction of two new uorb messages to log the content of UBX_MSG_MON_SPAN and UBX_MON_RF on a per-band level per receiver.
Gated behind Kconfig GPS_UBX_SPAN and new parameter GPS_UBX_SPECTRUM.
* feat(gps): deactivate Kconfig option GPS_UBX_SPAN by default
Co-authored-by: Jacob Dahl <37091262+dakejahl@users.noreply.github.com>
* feat(gps): update submodule
* chore(gps): remove unnecessary comment and whitespace
* feat(boards/px4_fmu-v6c): activate CONFIG_GPS_UBX_SPAN
---------
Co-authored-by: Jacob Dahl <37091262+dakejahl@users.noreply.github.com>
Introduces a general UAVCAN_QUIRKS bitmask so workarounds for other
non-compliant DroneCAN devices can be added as new bits without
introducing a new parameter each time. Bit 0 covers the Hobbywing
ESC esc_index off-by-one quirk.
Use the fixed-wing-frame yaw already computed from vehicle attitude for the no-wind heading and track quality check. Use the same adapted yaw when capturing the no-position backtransition heading.
Assisted-by: Codex:gpt-5
Signed-off-by: Kirill <exxxim@gmail.com>
Co-authored-by: Kirill <exxxim@gmail.com>
The route projection test assigns the float NAN macro to double latitude
fields and the vision target estimator test assigns a float altitude to
the double altitude_msl_m field. The macOS toolchain rejects both under
-Wdouble-promotion with -Werror, so the test suite stopped building there
after #28248 and #28552. Cast at the three sites, as the other tests do.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
This adds siyi_unifc-6-pico flight controller board in addition to the docs provided for the same board in this PR.
Co-authored-by: zhucaigui <zhucaigui@siyi.biz>
Shorten the cmake, cdev and gps_blending comments, drop the unreachable
snprintf clamp in test_uart_send and simplify the pure pursuit bearing
assertions to the absolute value with a 1e-6 tolerance, which covers both
sides of the +-pi cut. Cosmetic, no behaviour change.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Saibernard Yogendran <bernie97@seas.upenn.edu>
fuseHaglRng() stamped aid_src.time_last_fuse and _time_last_terrain_fuse
but never _time_last_hgt_fuse, so range height fusion did not count as a
vertical position fusion. With the range finder as the only height source
isHeightResetRequired() was therefore permanently true, and
controlRangeHaglFusion() reset the altitude every hgt_fusion_timeout_max
for the whole flight while range height fusion was running and healthy.
Stamp the timeout in fuseHaglRng() when the height state is updated, the
same way fuseVerticalPosition() stamps it for the baro, GNSS altitude and
vision height, and the way the terrain timeout is stamped three lines
below. Terrain-only range fusion zeroes every Kalman gain except terrain,
so it is gated on update_height and does not count as a height fusion.
Range height fusion has been on this path since the terrain state moved
into the main filter in 68980b59e2.
EkfTerrainTest.testHeightReset expected a vertical position reset onto a
baro that had just jumped 50 m while a healthy range finder was fusing
height. That reset only happened because the timeout was stale. The baro
is now latched faulty instead and the altitude stays on the range finder,
so the expectation is updated.
Assisted-by: Claude:claude-opus-5[1m]
Signed-off-by: Ramon Roche <mrpollo@gmail.com>
* feat(navigator): mission route planning library
* fix(navigator): account for implicit FT with VTOL_TAKEOFF, properly inverse projections, and handle terminal jumps
* feat(navigator): handle stacked waypoints above takeoff and land (to avoid extra climbs)
---------
Co-authored-by: Jonas <jonas.perolini@rigi.tech>
* feat(navigator): precision takeoff with the vision target estimator
* fix(navigator): only remove the liftoff freeze when an actual target is detected
* feat(logger): log prec_takeoff_status as optional topic
* chore(vte): minor format
* fix(logger): move to topic behind CONFIG_MODULES_VISION_TARGET_ESTIMATOR
* fix(navigator): preserve corrected position on takeoff handoff
* docs(vte): rephrase VTE_AID_MASK
* rework(vte): clarify that home is used as the pad's abs position during prec takeoff
* fix(vte): clear cached relative mission position once task starts
* fix(takeoff): keep xy freez until TAKEOFF_STATE_FLIGHT
* feat(navigator): delay prec takeoff corrections by MIS_TKO_PREC_DLY
---------
Co-authored-by: jonas <jonas.perolini@rigi.tech>
* feat(mavlink): stream manual input status which specifies the active source for manual control setpoint generation on the autopilot
---------
Signed-off-by: RomanBapst <bapstroman@gmail.com>
Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
Co-authored-by: Matthias Grob <maetugr@gmail.com>
* fix(commander): scale mag cal offset limit with sensor full-scale range
The hard 1.3 Ga offset limit assumes ~1.9 Ga full scale, so modern mags (5-49 Ga full scale) fail calibration on large hard-iron offsets they can measure without saturating.
Drivers now report their range in sensor_mag and the limit becomes range minus
0.65 Ga worst-case earth field, keeping the 1.3 Ga fallback when the
range is unknown.
* fix(drivers): correct ADIS16448 and AK8963 mag ranges
* refactor(sensors): address review comments
- kConstant cal limits
- uT conversion helper