diff --git a/CodeQL.yml b/CodeQL.yml new file mode 100644 index 000000000..6e78ab59d --- /dev/null +++ b/CodeQL.yml @@ -0,0 +1,10 @@ +queries: + # Exclude all external PHP SDK source code + - exclude: + path: + - "buildscripts/php-sdk" + # Include only the SQLSRV and PDO_SQLSRV source files within the PHP SDK, where we drop them for compilation + - include: + path: + - "buildscripts/php-sdk/**/sqlsrv" + - "buildscripts/php-sdk/**/pdo_sqlsrv" diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index ed67b8681..3b18b8fcd 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1462,7 +1462,7 @@ struct sqlsrv_param void copy_param_meta_ae(_Inout_ zval* param_z, _In_ param_meta_data& meta); // Only used when Always Encrypted is enabled - virtual ~sqlsrv_param(){ release_data(); } + virtual ~sqlsrv_param(){ sqlsrv_param::release_data(); } virtual void release_data(); bool derive_string_types_sizes(_In_ zval* param_z); @@ -1539,7 +1539,7 @@ struct sqlsrv_param_tvp : public sqlsrv_param { ZVAL_UNDEF(&placeholder_z); } - virtual ~sqlsrv_param_tvp() { release_data(); } + virtual ~sqlsrv_param_tvp() { sqlsrv_param_tvp::release_data(); } virtual void release_data(); virtual void bind_param(_Inout_ sqlsrv_stmt* stmt); virtual void process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index bc8e940da..4ea6a638f 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2911,9 +2911,10 @@ void sqlsrv_param_inout::finalize_output_string() char* outString = NULL; SQLLEN outLen = 0; - // When encoding is UTF-8 or SYSTEM, ODBC returns data as UTF-16 wide characters - // in the output parameter buffer. ODBC guarantees proper alignment and valid UTF-16 data. - const SQLWCHAR* wide_str = reinterpret_cast(str); + // For UTF-8/SYSTEM encoding, the output parameter buffer was bound with SQL_C_WCHAR via + // SQLBindParameter, so ODBC wrote UTF-16 data into it. The reinterpret_cast is necessary + // because the ODBC API binds output params to a generic char* buffer. + const SQLWCHAR* wide_str = reinterpret_cast(str); // CodeQL [SM02986] buffer contains UTF-16 data from ODBC output parameter bound with SQL_C_WCHAR bool result = convert_string_from_utf16(encoding, wide_str, int(str_len / sizeof(SQLWCHAR)), &outString, outLen); CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message(), NULL) { throw core::CoreException(); diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index aebbbfe2c..44648c04d 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -200,10 +200,10 @@ size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) throw core::CoreException(); } -// When encoding is UTF-8, SQLGetData fills temp_buf with UTF-16 wide character data (SQL_C_WCHAR). - // The buffer is allocated as char* but contains properly aligned UTF-16 data from ODBC. - // Reinterpret as LPCWSTR for conversion functions. This is safe because ODBC guarantees proper alignment. - const LPCWSTR wide_buffer = reinterpret_cast( temp_buf.get() ); + // temp_buf contains UTF-16 data written by the ODBC driver via SQLGetData with SQL_C_WCHAR + // (set at line 88 when ss->encoding == CP_UTF8). The char* buffer is a generic allocation; + // the reinterpret_cast is necessary because the ODBC API uses SQLPOINTER (void*) buffers. + const LPCWSTR wide_buffer = reinterpret_cast( temp_buf.get() ); // CodeQL [SM02986] buffer contains UTF-16 data from ODBC SQLGetData with SQL_C_WCHAR #ifndef _WIN32 int enc_len = SystemLocale::FromUtf16( ss->encoding, wide_buffer, static_cast(read >> 1), buf, static_cast(count), NULL, NULL ); diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 2ff4c2b47..1ac592e04 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -106,9 +106,11 @@ bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_up char* outString = NULL; SQLLEN outLen = 0; - // The string buffer contains UTF-16 encoded data. Reinterpret as SQLWCHAR* for conversion. - // This is safe because the caller ensures the buffer contains valid UTF-16 data. - const SQLWCHAR* wide_str = reinterpret_cast(*string); + // The buffer contains UTF-16 data written by the ODBC driver via SQLGetData with SQL_C_WCHAR. + // The only caller (get_field_as_string in core_stmt.cpp) guards this call with + // "if (c_type == SQL_C_WCHAR)", ensuring the buffer was populated as UTF-16 by the driver. + // The reinterpret_cast is necessary because the ODBC API uses a generic char* buffer. + const SQLWCHAR* wide_str = reinterpret_cast(*string); // CodeQL [SM02986] buffer is ensured to contain UTF-16 data bool result = convert_string_from_utf16( encoding, wide_str, int(len / sizeof(SQLWCHAR)), &outString, outLen ); if (result) diff --git a/test/tools/mock_tds_server.py b/test/tools/mock_tds_server.py index b2a7bd81d..731478e12 100644 --- a/test/tools/mock_tds_server.py +++ b/test/tools/mock_tds_server.py @@ -1341,7 +1341,7 @@ def generate_self_signed_cert(cert_path, key_path): .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(datetime.datetime.now(datetime.timezone.utc)) - .not_valid_after(datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365)) + .not_valid_after(datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=30)) .sign(key, hashes.SHA256()) ) with open(key_path, "wb") as f: