fix: guard per-filament array reads against short config arrays (#14789)

* fix: guard H2C per-filament array reads against short config arrays

The H2C tool-ordering, wipe-tower, and g-code export paths index per-filament
config arrays by filament/tool id. A config with fewer entries than the filament
count (partial or legacy projects, minimal test configs) makes these reads run
past the end of the vector: silent under a normal STL, but UB that aborts under
the flatpak build's bounds-checked STL (_GLIBCXX_ASSERTIONS).

Route the reads through the existing clamping accessors (get_at,
get_filament_category, is_in_same_extruder) and add a small clamp helper for
filament_change_length. The guards are no-ops when the arrays are sized to the
filament count, so correctly specified configs are unaffected.

* fix: size the grouping context's filament_info to the filament count

build_filament_group_context built model_info.filament_info by walking
filament_type, so a config whose filament_type is shorter than the filament
count produced a short vector. FilamentGroup indexes filament_info by filament
id, so clamping the individual reads only moved the out-of-bounds access
downstream. Loop to filament_nums and read all three fields through get_at,
and drop filament_ids entries past the filament count, since the grouping code
pairs filament_ids and filament_info by position.

Adds a regression test with four filaments and one-entry filament_type /
filament_is_support. Without the fix it throws bad_alloc from copying a garbage
std::string read past the end.

* fix: guard the carousel nozzle-change length reads too

The carousel branch added in b90ac13d86/b0dddb4648 reads
m_filaments_change_length by tool id without a bounds check, the same
pattern this branch already routed through filament_change_length_at
a few lines above in both plan_toolchange and plan_tower_new.

* fix: guard WipeTower per-filament array reads against short config arrays

The BambuStudio WipeTower sync reintroduced raw per-filament array
indexing that reads out of bounds when a config leaves an array shorter
than the filament count: m_physical_extruder_map in format_line_M104/M109
(indexed even when empty), and m_filament_categories in get_wall_skip_points
and get_wall_filament_for_all_layer. Silent on a normal STL, a hard abort
under the bounds-checked STL the Flatpak build uses.

Bounds-check the physical extruder map before indexing (omitting the T
token, as the existing -1 path already does), and route the two raw
m_filament_categories reads through the clamping get_filament_category()
accessor the surrounding code already uses. No change for correctly-sized
configs.
This commit is contained in:
Kris Austin
2026-09-15 09:03:20 -03:00
committed by GitHub
parent bb3a260acb
commit bd1304443c
4 changed files with 61 additions and 14 deletions
+2 -2
View File
@@ -3555,7 +3555,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
auto used_filaments = print.get_slice_used_filaments(false);
this->placeholder_parser().set("is_all_bbl_filament", std::all_of(used_filaments.begin(), used_filaments.end(), [&](auto idx) {
return m_config.filament_vendor.values[idx] == "Bambu Lab";
return m_config.filament_vendor.get_at(idx) == "Bambu Lab";
}));
//add during_print_exhaust_fan_speed
@@ -3572,7 +3572,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
this->placeholder_parser().set("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed));
auto first_layer_filaments = print.get_slice_used_filaments(true);
bool has_tpu_in_first_layer = std::any_of(first_layer_filaments.begin(), first_layer_filaments.end(), [&](unsigned int idx) { return m_config.filament_type.values[idx] == "TPU"; });
bool has_tpu_in_first_layer = std::any_of(first_layer_filaments.begin(), first_layer_filaments.end(), [&](unsigned int idx) { return m_config.filament_type.get_at(idx) == "TPU"; });
this->placeholder_parser().set("has_tpu_in_first_layer", new ConfigOptionBool(has_tpu_in_first_layer));
if (print.calib_params().mode == CalibMode::Calib_PA_Line) {
+11 -8
View File
@@ -1488,10 +1488,10 @@ static FilamentGroupContext build_filament_group_context(
auto machine_filament_info = build_machine_filaments(print->get_extruder_filament_info(), extruder_ams_counts, ignore_ext_filament);
std::vector<std::string> filament_types = print_config.filament_type.values;
std::vector<std::string> filament_colours = print_config.filament_colour.values;
std::vector<unsigned char> filament_is_support = print_config.filament_is_support.values;
std::vector<std::string> filament_ids = print_config.filament_ids.values;
// The grouping code walks filament_ids and indexes filament_info by the same position.
std::vector<std::string> filament_ids = print_config.filament_ids.values;
if (filament_ids.size() > filament_nums)
filament_ids.resize(filament_nums);
FGMode fg_mode = mode == FilamentMapMode::fmmAutoForMatch ? FGMode::MatchMode : FGMode::FlushMode;
context.model_info.flush_matrix = std::move(nozzle_flush_mtx);
@@ -1500,11 +1500,14 @@ static FilamentGroupContext build_filament_group_context(
context.model_info.filament_ids = filament_ids;
context.model_info.unprintable_volumes = unprintable_volumes;
for (size_t idx = 0; idx < filament_types.size(); ++idx) {
// Consumers index filament_info by filament id, so it must span the filament count: a partial
// or legacy config can leave any of these arrays short, and get_at clamps.
context.model_info.filament_info.reserve(filament_nums);
for (size_t idx = 0; idx < filament_nums; ++idx) {
FilamentGroupUtils::FilamentInfo info;
info.color = filament_colours[idx];
info.type = filament_types[idx];
info.is_support = filament_is_support[idx];
info.color = print_config.filament_colour.get_at(idx);
info.type = print_config.filament_type.get_at(idx);
info.is_support = print_config.filament_is_support.get_at(idx);
context.model_info.filament_info.emplace_back(std::move(info));
}
+4 -4
View File
@@ -1349,7 +1349,7 @@ public:
// flavor it reaches understands, not the zero dwell the other flavors flush with.
buffer += "M400\n";
buffer += "M104";
if (target_extruder != -1)
if (target_extruder != -1 && target_extruder < int(m_physical_extruder_map.size()))
buffer += (" T" + std::to_string(m_physical_extruder_map[target_extruder]));
buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by slicer
if (!comment.empty()) buffer += " ;" + comment;
@@ -1361,7 +1361,7 @@ public:
WipeTowerWriter &format_line_M109(int target_temp, int target_extruder, const std::string &comment = std::string())
{
std::string buffer = "M109";
if (target_extruder != -1)
if (target_extruder != -1 && target_extruder < int(m_physical_extruder_map.size()))
buffer += (" T" + std::to_string(m_physical_extruder_map[target_extruder]));
buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by slicer
if (!comment.empty()) buffer += " ;" + comment;
@@ -3309,7 +3309,7 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer, int layer_id)
if (!cur_block_depth.count(m_filpar[new_filament].category)) cur_block_depth[m_filpar[new_filament].category] = block->start_depth;
process_depth = cur_block_depth[m_filpar[new_filament].category];
if (is_need_ramming(new_filament, old_filament, layer_id)) {
if (m_filament_categories[new_filament] == m_filament_categories[old_filament])
if (get_filament_category(new_filament) == get_filament_category(old_filament))
process_depth += nozzle_change_depth;
else {
if (!cur_block_depth.count(m_filpar[old_filament].category)) {
@@ -4783,7 +4783,7 @@ int WipeTower::get_wall_filament_for_all_layer()
int filament_id = -1;
int filament_count = 0;
for (auto iter = filament_counts.begin(); iter != filament_counts.end(); ++iter) {
if (m_filament_categories[iter->first] == selected_category && iter->second > filament_count) {
if (get_filament_category(iter->first) == selected_category && iter->second > filament_count) {
filament_id = iter->first;
filament_count = iter->second;
}