Skip to content

[BUG] cuOptGetDualSolution writes beyond the documented num_constraints buffer #1751

Description

@mlubin

Summary

For a problem containing a quadratic constraint, cuOptGetDualSolution
writes more elements than reported by cuOptGetNumConstraints.

This overruns a correctly sized caller-owned buffer.

Environment

  • cuOpt 26.08.0
  • Git hash: 400863c1
  • CUDA 13.3
  • GPU: NVIDIA RTX PRO 6000 Blackwell Workstation Edition

Standalone C reproducer

#include <cuopt/mathematical_optimization/cuopt_c.h>

#include <math.h>
#include <stdio.h>

int main(void)
{
    cuOptOptimizationProblem problem = NULL;
    cuOptSolverSettings settings = NULL;
    cuOptSolution solution = NULL;

    /*
     * minimize t
     *
     * subject to
     *     t >= 0
     *     x1^2 + x2^2 - t^2 <= 0
     *     t >= -1
     *     x1 >= 3
     *     x2 >= 4
     */
    const cuopt_int_t num_constraints = 1;
    const cuopt_int_t num_variables = 3;

    const cuopt_float_t objective[] = {1.0, 0.0, 0.0};

    const cuopt_int_t row_offsets[] = {0, 1};
    const cuopt_int_t column_indices[] = {0};
    const cuopt_float_t matrix_values[] = {1.0};
    const char constraint_sense[] = {CUOPT_GREATER_THAN};
    const cuopt_float_t rhs[] = {0.0};

    const cuopt_float_t lower_bounds[] = {-1.0, 3.0, 4.0};
    const cuopt_float_t upper_bounds[] = {
        CUOPT_INFINITY,
        CUOPT_INFINITY,
        CUOPT_INFINITY
    };
    const char variable_types[] = {
        CUOPT_CONTINUOUS,
        CUOPT_CONTINUOUS,
        CUOPT_CONTINUOUS
    };

    cuopt_int_t status = cuOptCreateProblem(
        num_constraints,
        num_variables,
        CUOPT_MINIMIZE,
        0.0,
        objective,
        row_offsets,
        column_indices,
        matrix_values,
        constraint_sense,
        rhs,
        lower_bounds,
        upper_bounds,
        variable_types,
        &problem
    );
    if (status != CUOPT_SUCCESS) {
        printf("cuOptCreateProblem failed: %d\n", status);
        return 1;
    }

    const cuopt_int_t qc_row[] = {0, 1, 2};
    const cuopt_int_t qc_col[] = {0, 1, 2};
    const cuopt_float_t qc_coeff[] = {-1.0, 1.0, 1.0};

    status = cuOptAddQuadraticConstraint(
        problem,
        3,
        qc_row,
        qc_col,
        qc_coeff,
        0,
        NULL,
        NULL,
        CUOPT_LESS_THAN,
        0.0
    );
    if (status != CUOPT_SUCCESS) {
        printf("cuOptAddQuadraticConstraint failed: %d\n", status);
        return 1;
    }

    cuopt_int_t queried_num_constraints = -1;
    status = cuOptGetNumConstraints(problem, &queried_num_constraints);
    if (status != CUOPT_SUCCESS) {
        printf("cuOptGetNumConstraints failed: %d\n", status);
        return 1;
    }

    printf("cuOptGetNumConstraints = %d\n", queried_num_constraints);

    status = cuOptCreateSolverSettings(&settings);
    if (status != CUOPT_SUCCESS) {
        printf("cuOptCreateSolverSettings failed: %d\n", status);
        return 1;
    }

    status = cuOptSetIntegerParameter(
        settings,
        CUOPT_METHOD,
        CUOPT_METHOD_BARRIER
    );
    if (status != CUOPT_SUCCESS) {
        printf("cuOptSetIntegerParameter failed: %d\n", status);
        return 1;
    }

    status = cuOptSolve(problem, settings, &solution);
    if (status != CUOPT_SUCCESS) {
        printf("cuOptSolve failed: %d\n", status);
        return 1;
    }

    /*
     * Use an oversized sentinel buffer so the out-of-bounds writes can be
     * observed without corrupting unrelated memory.
     */
    const cuopt_float_t sentinel = 1.234567890123456e200;
    cuopt_float_t guarded[64];

    for (int i = 0; i < 64; ++i) {
        guarded[i] = sentinel;
    }

    status = cuOptGetDualSolution(solution, guarded);
    printf("cuOptGetDualSolution status = %d\n", status);

    for (int i = 0; i < 64; ++i) {
        if (guarded[i] != sentinel) {
            printf("changed[%d] = %g\n", i, guarded[i]);
        }
    }

    cuOptDestroySolution(&solution);
    cuOptDestroySolverSettings(&settings);
    cuOptDestroyProblem(&problem);

    return 0;
}

Output

cuOptGetNumConstraints = 1
cuOptGetDualSolution status = 0
changed[0] = nan
changed[1] = nan
changed[2] = nan
changed[3] = nan

Recommended fix options:

  • Return invalid argument when querying cuOptGetDualSolution in this scenario
  • Copy only numConstraints elements into the solution vector

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions