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
2 changes: 1 addition & 1 deletion ci/utils/aggregate_nightly.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ def main():
)

args = parser.parse_args()
output_dir = Path(args.output_dir)
output_dir = Path(args.output_dir).resolve()
output_dir.mkdir(parents=True, exist_ok=True)

# ---- Step 1: Collect summaries ----
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/dual_simplex/phase2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1790,7 +1790,7 @@ i_t compute_delta_x(const lp_problem_t<i_t, f_t>& lp,

f_t scale = scaled_delta_xB_sparse.find_coefficient(basic_leaving_index);
work_estimate += 2 * scaled_delta_xB_sparse.i.size();
if (scale != scale) {
if (std::isnan(scale)) {
// We couldn't find a coefficient for the basic leaving index.
// The coefficient might be very small. Switch to a regular solve and try to recover.
std::vector<f_t> rhs;
Expand Down
5 changes: 3 additions & 2 deletions cpp/src/io/experimental_mps_fast/fast_fp64_parser.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#pragma once
Expand All @@ -8,6 +8,7 @@
#include <array>
#include <bit>
#include <cerrno>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
Expand Down Expand Up @@ -423,7 +424,7 @@ static inline double parse_fp64_advance(const char*& p, const char* end)
}

double v = assemble_fp64(dec);
if (v == v) {
if (!std::isnan(v)) {
if (p < end && (unsigned char)*p > 32) {
mps_parser_fail(error_type_t::ValidationError, "Invalid or out-of-range MPS numeric token");
}
Expand Down
21 changes: 12 additions & 9 deletions cpp/src/io/experimental_mps_fast/fast_parser.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// reserved. SPDX-License-Identifier: Apache-2.0

#include "fast_parser.hpp"
Expand Down Expand Up @@ -221,13 +221,16 @@ class scoped_timer_t {
~scoped_timer_t()
{
#ifdef MPS_FAST_TIMERS
auto end = std::chrono::high_resolution_clock::now();
double elapsed_ms = std::chrono::duration<double, std::milli>(end - start_).count();
nvtx_.end();
if (accumulator_) { *accumulator_ += elapsed_ms; }
auto [rss_kb, hwm_kb] = current_process_rss_kb();
std::lock_guard<std::mutex> lock(get_timer_mutex());
get_timer_buffer().push_back({name_, elapsed_ms, rss_kb, hwm_kb});
try {
auto end = std::chrono::high_resolution_clock::now();
double elapsed_ms = std::chrono::duration<double, std::milli>(end - start_).count();
nvtx_.end();
if (accumulator_) { *accumulator_ += elapsed_ms; }
auto [rss_kb, hwm_kb] = current_process_rss_kb();
std::lock_guard<std::mutex> lock(get_timer_mutex());
get_timer_buffer().push_back({name_, elapsed_ms, rss_kb, hwm_kb});
} catch (...) {
}
#endif
}

Expand Down Expand Up @@ -1199,7 +1202,7 @@ static const char* find_line_start(const char* section_start, const char* p)
{
while (p > section_start && p[-1] != '\n')
--p;
return p;
return p; // NOSONAR: pointer stays within [section_start, original_p]; guard prevents underflow
}

static std::vector<bounds_chunk_boundary_t> compute_bounds_chunk_boundaries(
Expand Down
8 changes: 5 additions & 3 deletions cpp/src/io/experimental_mps_fast/lz4_file_reader.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// reserved. SPDX-License-Identifier: Apache-2.0

#include "file_reader.hpp"
Expand Down Expand Up @@ -452,9 +452,11 @@ struct lz4_pipeline_t {
: input(input_),
window_count(cuda::ceil_div(input.compressed_size_, window_bytes)),
windows(window_count),
window_state_(std::make_unique<window_state_t[]>(window_count)),
window_state_(std::make_unique<window_state_t[]>(
window_count)), // NOSONAR: window_count declared before window_state_ (line 869 vs 891)
io_threads(std::min(lz4_input_max_io_threads, window_count)),
window_done(window_count, 0)
window_done(window_count,
0) // NOSONAR: window_count declared before window_done (line 869 vs 880)
{
for (std::size_t i = 0; i < window_count; ++i) {
std::size_t offset = i * window_bytes;
Expand Down
66 changes: 49 additions & 17 deletions cpp/src/io/file_to_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ std::vector<char> bz2_file_to_string(const std::string& file)
void operator()(void* f)
{
int bzerror;
if (f != nullptr) fptr(&bzerror, f);
if (f != nullptr)
fptr(&bzerror,
f); // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
mps_parser_expects_fatal(
bzerror == BZ_OK, error_type_t::ValidationError, "Error closing bzip2 file!");
}
Expand Down Expand Up @@ -105,7 +107,9 @@ std::vector<char> bz2_file_to_string(const std::string& file)
file.c_str());
int bzerror = BZ_OK;
std::unique_ptr<void, BzReadCloseDeleter> bzfile{
BZ2_bzReadOpen(&bzerror, fp.get(), 0, 0, nullptr, 0), {BZ2_bzReadClose}};
BZ2_bzReadOpen(&bzerror, fp.get(), 0, 0, nullptr, 0),
{BZ2_bzReadClose}}; // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is
// reached
mps_parser_expects(bzerror == BZ_OK,
error_type_t::ValidationError,
"Could not open bzip2 compressed file! Given path: %s",
Expand All @@ -115,7 +119,12 @@ std::vector<char> bz2_file_to_string(const std::string& file)
const size_t readbufsize = 1ull << 24; // 16MiB - just a guess.
std::vector<char> readbuf(readbufsize);
while (bzerror == BZ_OK) {
const size_t bytes_read = BZ2_bzRead(&bzerror, bzfile.get(), readbuf.data(), readbuf.size());
const size_t bytes_read = BZ2_bzRead(
&bzerror,
bzfile.get(),
readbuf.data(),
readbuf
.size()); // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
if (bzerror == BZ_OK || bzerror == BZ_STREAM_END) {
buf.insert(buf.end(), begin(readbuf), begin(readbuf) + bytes_read);
}
Expand Down Expand Up @@ -150,7 +159,8 @@ std::vector<char> zlib_file_to_string(const std::string& file)
struct GzCloseDeleter {
void operator()(gzFile_s* f)
{
int err = fptr(f);
int err =
fptr(f); // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
mps_parser_expects_fatal(
err == Z_OK, error_type_t::ValidationError, "Error closing gz file!");
}
Expand All @@ -177,12 +187,15 @@ std::vector<char> zlib_file_to_string(const std::string& file)
"Error loading zlib! Library version might be incompatible. Please decompress the .gz file "
"manually and open the uncompressed file. Given path: %s",
file.c_str());
std::unique_ptr<gzFile_s, GzCloseDeleter> gzfp{gzopen(file.c_str(), "rb"), {gzclose_r}};
std::unique_ptr<gzFile_s, GzCloseDeleter> gzfp{
gzopen(file.c_str(), "rb"),
{gzclose_r}}; // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
mps_parser_expects(gzfp != nullptr,
error_type_t::ValidationError,
"Error opening compressed input file! Given path: %s",
file.c_str());
int zlib_status = gzbuffer(gzfp.get(), 1 << 20); // 1 MiB
int zlib_status = gzbuffer(gzfp.get(), 1 << 20); // 1 MiB // NOSONAR: mps_parser_expects/LZ4F
// guards above throw before this is reached
mps_parser_expects(zlib_status == Z_OK,
error_type_t::ValidationError,
"Could not set zlib internal buffer size for decompression! Given path: %s",
Expand All @@ -192,10 +205,13 @@ std::vector<char> zlib_file_to_string(const std::string& file)
std::vector<char> readbuf(readbufsize);
int bytes_read = -1;
while (bytes_read != 0) {
bytes_read = gzread(gzfp.get(), readbuf.data(), readbuf.size());
bytes_read = gzread(
gzfp.get(), readbuf.data(), readbuf.size()); // NOSONAR: mps_parser_expects/LZ4F guards above
// throw before this is reached
if (bytes_read > 0) { buf.insert(buf.end(), begin(readbuf), begin(readbuf) + bytes_read); }
if (bytes_read < 0) {
gzerror(gzfp.get(), &zlib_status);
gzerror(gzfp.get(), &zlib_status); // NOSONAR: mps_parser_expects/LZ4F guards above throw
// before this is reached
break;
}
}
Expand Down Expand Up @@ -247,7 +263,8 @@ std::vector<char> lz4_file_to_string(const std::string& file)
void operator()(LZ4F_dctx* f)
{
if (f != nullptr) {
const LZ4F_errorCode_t err = fptr(f);
const LZ4F_errorCode_t err =
fptr(f); // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
mps_parser_expects_fatal(
!is_error(err), error_type_t::ValidationError, "Error closing lz4 file!");
}
Expand Down Expand Up @@ -317,20 +334,29 @@ std::vector<char> lz4_file_to_string(const std::string& file)

constexpr unsigned lz4f_version = 100;
LZ4F_dctx* raw_dctx = nullptr;
LZ4F_errorCode_t lz4_status = LZ4F_createDecompressionContext(&raw_dctx, lz4f_version);
mps_parser_expects(!LZ4F_isError(lz4_status),
error_type_t::ValidationError,
"Could not open lz4 compressed file '%s': %s",
file.c_str(),
LZ4F_getErrorName(lz4_status));
LZ4F_errorCode_t lz4_status = LZ4F_createDecompressionContext(
&raw_dctx,
lz4f_version); // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
mps_parser_expects(
!LZ4F_isError(
lz4_status), // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
error_type_t::ValidationError,
"Could not open lz4 compressed file '%s': %s",
file.c_str(),
LZ4F_getErrorName(
lz4_status)); // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
std::unique_ptr<LZ4F_dctx, Lz4DctxDeleter> dctx{raw_dctx,
{LZ4F_freeDecompressionContext, LZ4F_isError}};

const char* src = compressed.data();
size_t src_size = compressed.size();
LZ4F_frameInfo_t frame_info{};
size_t src_used = src_size;
lz4_status = LZ4F_getFrameInfo(dctx.get(), &frame_info, src, &src_used);
lz4_status = LZ4F_getFrameInfo(
dctx.get(),
&frame_info,
src,
&src_used); // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
mps_parser_expects(!LZ4F_isError(lz4_status),
error_type_t::ValidationError,
"Error reading lz4 frame info for input file '%s': %s",
Expand All @@ -346,7 +372,13 @@ std::vector<char> lz4_file_to_string(const std::string& file)
while (lz4_status != 0) {
size_t dst_size = readbuf.size();
src_used = src_size;
lz4_status = LZ4F_decompress(dctx.get(), readbuf.data(), &dst_size, src, &src_used, nullptr);
lz4_status = LZ4F_decompress(
dctx.get(),
readbuf.data(),
&dst_size,
src,
&src_used,
nullptr); // NOSONAR: mps_parser_expects/LZ4F guards above throw before this is reached
mps_parser_expects(!LZ4F_isError(lz4_status),
error_type_t::ValidationError,
"Error in lz4 decompression of input file '%s': %s",
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/utilities/pcgenerator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class pcgenerator_t {
state = oldstate * 6364136223846793005ULL + stream;
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
uint32_t rot = oldstate >> 59u;
ret = (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
ret = (xorshifted >> rot) | (xorshifted << ((32u - rot) & 31u));
return ret;
}

Expand Down
3 changes: 2 additions & 1 deletion cpp/tests/linear_programming/c_api_tests/c_api_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <cuopt/mathematical_optimization/cuopt_c.h>

#include <cuda_runtime.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -262,7 +263,7 @@ static cuopt_int_t test_mip_callbacks_internal(int include_set_callback)
goto DONE;
}

if (context.last_solution_bound != context.last_solution_bound) {
if (isnan(context.last_solution_bound)) {
printf("Error reading solution bound in callback\n");
status = CUOPT_INVALID_ARGUMENT;
goto DONE;
Expand Down
84 changes: 76 additions & 8 deletions python/cuopt_server/cuopt_server/webserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,15 @@ def health():

# Get name for file that stores the result of Solve
def get_output_name(resultdir, CUOPT_DATA_FILE, CUOPT_RESULT_FILE):
# Prevent absolute paths, or navigating with ../..
if CUOPT_RESULT_FILE.startswith("/") or ".." in CUOPT_RESULT_FILE:
CUOPT_RESULT_FILE = ""
# Reject paths that escape resultdir using canonicalized containment check.
if CUOPT_RESULT_FILE and resultdir:
root = os.path.realpath(resultdir)
candidate = os.path.realpath(os.path.join(root, CUOPT_RESULT_FILE))
if (
os.path.isabs(CUOPT_RESULT_FILE)
or os.path.commonpath([root, candidate]) != root
):
CUOPT_RESULT_FILE = ""
if not resultdir:
res = ""
elif CUOPT_RESULT_FILE:
Expand Down Expand Up @@ -343,6 +349,18 @@ def getsolverlogs(
f"supported values are {[mime_json, mime_msgpack, mime_zlib]}",
)

try:
uuid.UUID(id)
except ValueError:
raise HTTPException(
status_code=400, detail="Invalid request id format"
)

if frombyte < 0:
raise HTTPException(
status_code=422, detail="frombyte must be >= 0"
)

# result_dir is guaranteed to exist on startup
log_dir, _, _ = settings.get_result_dir()
log_fname = "log_" + id
Expand Down Expand Up @@ -448,6 +466,13 @@ def deletesolverlogs(
f"supported values are {[mime_json, mime_msgpack, mime_zlib]}",
)

try:
uuid.UUID(id)
except ValueError:
raise HTTPException(
status_code=400, detail="Invalid request id format"
)

# Delete the log for the request if the request is not done
log_dir, _, _ = settings.get_result_dir()
log_fname = "log_" + id
Expand Down Expand Up @@ -1316,6 +1341,52 @@ def cuopt(request: Request, data_bytes: bytes = Depends(get_body)):
)
)

# Canonicalize NVCF_LARGE_OUTPUT_DIR before use as a write directory.
# Headers are untrusted; resolve symlinks and require absolute paths.
# If CUOPT_NVCF_OUTPUT_ROOT is set, further require containment within
# that deployment-configured root.
large_output_dir = ""
if NVCF_LARGE_OUTPUT_DIR:
if not os.path.isabs(NVCF_LARGE_OUTPUT_DIR):
raise HTTPException(
status_code=400,
detail="nvcf-large-output-dir must be an absolute path",
)
large_output_dir = os.path.realpath(NVCF_LARGE_OUTPUT_DIR)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
nvcf_output_root = os.environ.get("CUOPT_NVCF_OUTPUT_ROOT", "")
if nvcf_output_root:
trusted_root = os.path.realpath(nvcf_output_root)
if (
os.path.commonpath([trusted_root, large_output_dir])
!= trusted_root
):
raise HTTPException(
status_code=400,
detail="nvcf-large-output-dir must be within the configured output root",
)

# Validate NVCF asset path before registering the result so that
# validation failures do not leave a registered-but-unreachable result.
file_path = None
if NVCF_FUNCTION_ASSET_IDS:
asset_id = NVCF_FUNCTION_ASSET_IDS.split(",")[0]
if not NVCF_ASSET_DIR:
raise HTTPException(
status_code=400,
detail="nvcf-asset-dir must be set when nvcf-function-asset-ids is provided",
)
asset_root = os.path.realpath(NVCF_ASSET_DIR)
candidate = os.path.realpath(os.path.join(asset_root, asset_id))
if (
os.path.isabs(asset_id)
or os.path.commonpath([asset_root, candidate]) != asset_root
):
raise HTTPException(
status_code=400,
detail="Asset path must stay within the asset directory",
)
file_path = candidate

# Create a NVCFJobResult to hold the solution
if os.environ.get("CUOPT_SERVER_TEST_LARGE_RESULT", False):
maxresult = 0
Expand All @@ -1324,13 +1395,10 @@ def cuopt(request: Request, data_bytes: bytes = Depends(get_body)):
maxresult = int(NVCF_MAX_RESPONSE_SIZE_BYTES) / 1000
except Exception:
_, maxresult, _ = settings.get_result_dir()
r = NVCFJobResult(NVCF_LARGE_OUTPUT_DIR, maxresult, accept)
r = NVCFJobResult(large_output_dir, maxresult, accept)
id = r.register_result()

if NVCF_FUNCTION_ASSET_IDS:
file_path = os.path.join(
NVCF_ASSET_DIR, NVCF_FUNCTION_ASSET_IDS.split(",")[0]
)
if file_path is not None:
job = SolverBinaryJobPath(
id,
warnings,
Expand Down
Loading