Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions cpp/libmps_parser/include/mps_parser/mps_data_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,40 @@ class mps_data_model_t {
const i_t* Q_offsets,
i_t size_offsets);

/**
* @brief CSR of Q for one quadratic constraint (MPS QCMATRIX).
*
* @c constraint_row_index is the row index in the linear constraint matrix A (0-based),
* matching the order of non-objective rows in the ROWS section.
*/
struct quadratic_constraint_matrix_t {
i_t constraint_row_index{};
std::vector<f_t> values;
std::vector<i_t> indices;
std::vector<i_t> offsets;
};

/**
* @brief Append one quadratic constraint matrix (QCMATRIX) in CSR format.
*
* @param constraint_row_index Row index in A (0-based), matching non-objective ROWS order.
* @param[in] Qc_values Values of the CSR representation; copied into the model.
* @param size_values Size of the Qc_values array.
* @param[in] Qc_indices Indices of the CSR representation; copied into the model.
* @param size_indices Size of the Qc_indices array.
* @param[in] Qc_offsets Offsets of the CSR representation; copied into the model.
* @param size_offsets Size of the Qc_offsets array.
*/
void append_quadratic_constraint_matrix(i_t constraint_row_index,
const f_t* Qc_values,
i_t size_values,
const i_t* Qc_indices,
i_t size_indices,
const i_t* Qc_offsets,
i_t size_offsets);

const std::vector<quadratic_constraint_matrix_t>& get_quadratic_constraint_matrices() const;

i_t get_n_variables() const;
i_t get_n_constraints() const;
i_t get_nnz() const;
Expand Down Expand Up @@ -306,6 +340,8 @@ class mps_data_model_t {

bool has_quadratic_objective() const noexcept;

bool has_quadratic_constraints() const noexcept;

/** whether to maximize or minimize the objective function */
bool maximize_;
/**
Expand Down Expand Up @@ -361,6 +397,9 @@ class mps_data_model_t {
std::vector<i_t> Q_objective_indices_;
std::vector<i_t> Q_objective_offsets_;

/** One CSR matrix per QCMATRIX block, in order of appearance in the file */
std::vector<quadratic_constraint_matrix_t> quadratic_constraint_matrices_;

}; // class mps_data_model_t

} // namespace cuopt::mps_parser
2 changes: 2 additions & 0 deletions cpp/libmps_parser/include/mps_parser/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ namespace cuopt::mps_parser {
* QPS files (for quadratic programming). QPS files are MPS files with additional
* sections:
* - QUADOBJ: Defines quadratic terms in the objective function
* - QMATRIX: Full symmetric quadratic objective matrix (alternative to QUADOBJ)
* - QCMATRIX: Symmetric quadratic terms for a named constraint row (QCQP)
*
* Note: Compressed MPS files .mps.gz, .mps.bz2 can only be read if the compression
* libraries zlib or libbzip2 are installed, respectively.
Expand Down
55 changes: 55 additions & 0 deletions cpp/libmps_parser/src/mps_data_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <utilities/error.hpp>

#include <algorithm>
#include <utility>

namespace cuopt::mps_parser {

Expand Down Expand Up @@ -219,6 +220,54 @@ void mps_data_model_t<i_t, f_t>::set_quadratic_objective_matrix(const f_t* Q_val
std::copy(Q_offsets, Q_offsets + size_offsets, Q_objective_offsets_.data());
}

template <typename i_t, typename f_t>
void mps_data_model_t<i_t, f_t>::append_quadratic_constraint_matrix(i_t constraint_row_index,
const f_t* Qc_values,
i_t size_values,
const i_t* Qc_indices,
i_t size_indices,
const i_t* Qc_offsets,
i_t size_offsets)
{
mps_parser_expects(
constraint_row_index >= 0, error_type_t::ValidationError, "constraint_row_index must be non-negative");

if (size_values != 0) {
mps_parser_expects(
Qc_values != nullptr, error_type_t::ValidationError, "Qc_values cannot be null");
}
if (size_indices != 0) {
mps_parser_expects(
Qc_indices != nullptr, error_type_t::ValidationError, "Qc_indices cannot be null");
}
mps_parser_expects(
Qc_offsets != nullptr, error_type_t::ValidationError, "Qc_offsets cannot be null");
mps_parser_expects(
size_offsets > 0, error_type_t::ValidationError, "size_offsets cannot be empty");

quadratic_constraint_matrix_t qcm;
qcm.constraint_row_index = constraint_row_index;
qcm.values.resize(size_values);
if (size_values > 0) {
std::copy(Qc_values, Qc_values + size_values, qcm.values.data());
}
qcm.indices.resize(size_indices);
if (size_indices > 0) {
std::copy(Qc_indices, Qc_indices + size_indices, qcm.indices.data());
}
qcm.offsets.resize(size_offsets);
std::copy(Qc_offsets, Qc_offsets + size_offsets, qcm.offsets.data());

quadratic_constraint_matrices_.push_back(std::move(qcm));
}
Comment on lines +223 to +262
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate non-negative CSR sizes before resize.

size_values and size_indices are signed (i_t). If either is negative, std::vector::resize can convert to a huge unsigned size and trigger resource exhaustion.

Proposed fix
 void mps_data_model_t<i_t, f_t>::append_quadratic_constraint_matrix(i_t constraint_row_index,
                                                                     const f_t* Qc_values,
                                                                     i_t size_values,
                                                                     const i_t* Qc_indices,
                                                                     i_t size_indices,
                                                                     const i_t* Qc_offsets,
                                                                     i_t size_offsets)
 {
   mps_parser_expects(
     constraint_row_index >= 0, error_type_t::ValidationError, "constraint_row_index must be non-negative");
+  mps_parser_expects(
+    size_values >= 0, error_type_t::ValidationError, "size_values cannot be negative");
+  mps_parser_expects(
+    size_indices >= 0, error_type_t::ValidationError, "size_indices cannot be negative");
+  mps_parser_expects(
+    size_offsets > 0, error_type_t::ValidationError, "size_offsets cannot be empty");

   if (size_values != 0) {
     mps_parser_expects(
       Qc_values != nullptr, error_type_t::ValidationError, "Qc_values cannot be null");
   }
   if (size_indices != 0) {
     mps_parser_expects(
       Qc_indices != nullptr, error_type_t::ValidationError, "Qc_indices cannot be null");
   }
   mps_parser_expects(
     Qc_offsets != nullptr, error_type_t::ValidationError, "Qc_offsets cannot be null");
-  mps_parser_expects(
-    size_offsets > 0, error_type_t::ValidationError, "size_offsets cannot be empty");

As per coding guidelines, "Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/libmps_parser/src/mps_data_model.cpp` around lines 223 - 262, In
append_quadratic_constraint_matrix, validate that size_values and size_indices
are non-negative before calling std::vector::resize and before using
Qc_values/Qc_indices: add mps_parser_expects(size_values >= 0, ...) and
mps_parser_expects(size_indices >= 0, ...) (similar to the existing checks for
constraint_row_index and size_offsets), then only call
qcm.values.resize(size_values) and std::copy(Qc_values, Qc_values + size_values,
...) when size_values > 0, and likewise only resize/copy indices when
size_indices > 0; this prevents signed-to-unsigned conversion into huge sizes
and avoids resource exhaustion in append_quadratic_constraint_matrix.


template <typename i_t, typename f_t>
auto mps_data_model_t<i_t, f_t>::get_quadratic_constraint_matrices() const
-> const std::vector<quadratic_constraint_matrix_t>&
{
return quadratic_constraint_matrices_;
}

template <typename i_t, typename f_t>
const std::vector<f_t>& mps_data_model_t<i_t, f_t>::get_constraint_matrix_values() const
{
Expand Down Expand Up @@ -460,6 +509,12 @@ bool mps_data_model_t<i_t, f_t>::has_quadratic_objective() const noexcept
return !Q_objective_values_.empty();
}

template <typename i_t, typename f_t>
bool mps_data_model_t<i_t, f_t>::has_quadratic_constraints() const noexcept
{
return !quadratic_constraint_matrices_.empty();
}

// NOTE: Explicitly instantiate all types here in order to avoid linker error
template class mps_data_model_t<int, float>;

Expand Down
Loading