diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 6e6f2ddc0..da4ca9001 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -18,6 +18,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- +#include extern "C" { #include "php_pdo_sqlsrv.h" @@ -25,20 +26,36 @@ extern "C" { #include "php_pdo_sqlsrv_int.h" +// Add safety constants +namespace { + const int MAX_KEY_LENGTH = 256; + const int MAX_VALUE_LENGTH = 65536; + const int MAX_DSN_LENGTH = 4096; +} + // Constructor conn_string_parser:: conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ) { + // Validate input parameters + if (dsn == nullptr || len < 0 || len > MAX_DSN_LENGTH || conn_options_ht == nullptr) { + THROW_PDO_ERROR(&ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING); + } + this->orig_str = dsn; this->len = len; this->element_ht = conn_options_ht; this->pos = -1; this->ctx = &ctx; this->current_key = 0; - this->current_key_name = NULL; } sql_string_parser:: sql_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholders_ht ) { + // Validate input parameters + if (sql_str == nullptr || len < 0 || len > MAX_DSN_LENGTH || placeholders_ht == nullptr) { + THROW_PDO_ERROR(&ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING); + } + this->orig_str = sql_str; this->len = len; this->element_ht = placeholders_ht; @@ -52,7 +69,6 @@ inline bool string_parser::next( void ) { // if already at the end then return false if( this->is_eos() ) { - return false; } @@ -61,7 +77,6 @@ inline bool string_parser::next( void ) this->pos++; if ( this->is_eos() ) { - return false; } @@ -93,10 +108,13 @@ inline bool string_parser::is_white_space( _In_ char c ) // Discard any trailing white spaces. int conn_string_parser::discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len ) { + if (str == nullptr || len <= 0) { + return 0; + } + const char* end = str + ( len - 1 ); while(( this->is_white_space( *end ) ) && (len > 0) ) { - len--; end--; } @@ -108,12 +126,10 @@ int conn_string_parser::discard_trailing_white_spaces( _In_reads_(len) const cha bool string_parser::discard_white_spaces() { if( this->is_eos() ) { - return false; } while( this->is_white_space( this->orig_str[pos] )) { - if( !next() ) return false; } @@ -124,16 +140,24 @@ bool string_parser::discard_white_spaces() // Add a key-value pair to the hashtable void string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ int len ) { + // Validate length + if (len < 0 || len > MAX_VALUE_LENGTH) { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, nullptr); + } + zval value_z; ZVAL_UNDEF( &value_z ); if( len == 0 ) { - ZVAL_STRINGL( &value_z, "", 0); } else { - - ZVAL_STRINGL( &value_z, const_cast( value ), len ); + // Ensure we don't pass nullptr to ZVAL_STRINGL + if (value == nullptr) { + ZVAL_STRINGL( &value_z, "", 0); + } else { + ZVAL_STRINGL( &value_z, const_cast( value ), len ); + } } core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z ); @@ -150,27 +174,54 @@ void sql_string_parser::add_key_int_value_pair( _In_ unsigned int value ) { // Validate a given DSN keyword. void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len ) { + // Validate input parameters + if (key == nullptr || key_len <= 0 || key_len > MAX_KEY_LENGTH) { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY); + } + int new_len = discard_trailing_white_spaces( key, key_len ); + // Validate trimmed length + if (new_len <= 0 || new_len > MAX_KEY_LENGTH) { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY); + } + for( int i=0; PDO_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) { // discard the null terminator. - if( new_len == ( PDO_CONN_OPTS[i].sqlsrv_len - 1 ) && !strncasecmp( key, PDO_CONN_OPTS[i].sqlsrv_name, new_len )) { - - this->current_key = PDO_CONN_OPTS[i].conn_option_key; - this->current_key_name = PDO_CONN_OPTS[i].sqlsrv_name; - return; + if( new_len == ( PDO_CONN_OPTS[i].sqlsrv_len - 1 ) ) { + // FIXED: Use safe manual comparison instead of strncasecmp + bool match = true; + for (int j = 0; j < new_len; ++j) { + if (std::tolower(static_cast(key[j])) != + std::tolower(static_cast(PDO_CONN_OPTS[i].sqlsrv_name[j]))) { + match = false; + break; + } + } + + if (match) { + this->current_key = PDO_CONN_OPTS[i].conn_option_key; + return; + } } } // encountered an invalid key, throw error. sqlsrv_malloc_auto_ptr key_name; - key_name = static_cast( sqlsrv_malloc( new_len + 1 )); - memcpy_s( key_name, new_len + 1 ,key, new_len ); - - key_name[new_len] = '\0'; - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast( key_name ), NULL ); + size_t alloc_size = new_len + 1; + if (alloc_size > 0 && alloc_size <= MAX_KEY_LENGTH + 1) { + key_name = static_cast( sqlsrv_malloc( alloc_size )); + if (key_name) { + memcpy_s( key_name, alloc_size, key, new_len ); + key_name[new_len] = '\0'; + + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast( key_name ), NULL ); + } + } + + // If memory allocation fails, throw generic error + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, "unknown", NULL ); } inline bool sql_string_parser::is_placeholder_char( char c ) @@ -186,9 +237,13 @@ inline bool sql_string_parser::is_placeholder_char( char c ) void conn_string_parser:: parse_conn_string( void ) { States state = FirstKeyValuePair; // starting state - int start_pos = -1; + int start_pos = 0; // FIXED: Initialize to 0 instead of -1 try { + // Validate overall DSN length + if (len <= 0 || len > MAX_DSN_LENGTH) { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING); + } while( !this->is_eos() ) { @@ -198,7 +253,6 @@ void conn_string_parser:: parse_conn_string( void ) { // discard leading spaces if( !next() || !discard_white_spaces() ) { - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING ); //EOS } @@ -214,12 +268,17 @@ void conn_string_parser:: parse_conn_string( void ) while( this->orig_str[pos] != '=' ) { if( !next() ) { - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY ); //EOS } } - this->validate_key( &( this->orig_str[start_pos] ), ( pos - start_pos ) ); + // Validate key length + int key_len = pos - start_pos; + if (key_len <= 0 || key_len > MAX_KEY_LENGTH) { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY); + } + + this->validate_key( &( this->orig_str[start_pos] ), key_len ); state = Value; @@ -239,11 +298,9 @@ void conn_string_parser:: parse_conn_string( void ) add_key_value_pair( NULL, 0 ); if( this->is_eos() ) { - break; // EOS } else { - // this->orig_str[pos] == ';' state = NextKeyValuePair; } @@ -268,11 +325,21 @@ void conn_string_parser:: parse_conn_string( void ) case ValueContent1: { - while ( this->orig_str[pos] != '}' ) { - + bool escaped_brace = false; + while ( this->orig_str[pos] != '}' || escaped_brace ) { + if ( ! next() ) { - - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, this->current_key_name, NULL ); + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_KEY, nullptr, nullptr ); + } + + // Handle escaped braces ({{ and }}) + if (this->orig_str[pos] == '}' && this->pos + 1 < len && this->orig_str[pos + 1] == '}') { + escaped_brace = true; + if (!next()) { + break; // Skip second brace + } + } else { + escaped_brace = false; } } @@ -284,21 +351,36 @@ void conn_string_parser:: parse_conn_string( void ) case ValueContent2: { + // Validate start_pos is within bounds + if (start_pos < 0 || start_pos >= len) { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, nullptr, nullptr); + } + while( this->orig_str[pos] != ';' ) { - + // FIXED: Validate characters in value content + if (this->orig_str[pos] == '{' || this->orig_str[pos] == '}') { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, nullptr, nullptr); + } + if( ! next() ) { - break; //EOS } } if( !this->is_eos() && this->orig_str[pos] == ';' ) { - // semi-colon encountered, so go to next key-value pair state = NextKeyValuePair; } - add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos ); + // Validate position before calculating length + if (start_pos >= 0 && start_pos <= this->pos) { + int value_len = this->pos - start_pos; + if (value_len >= 0 && value_len <= MAX_VALUE_LENGTH) { + add_key_value_pair( &( this->orig_str[start_pos] ), value_len ); + } else { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, nullptr, nullptr); + } + } SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )), "conn_string_parser::parse_conn_string: Invalid state encountered " ); @@ -308,12 +390,18 @@ void conn_string_parser:: parse_conn_string( void ) case RCBEncountered: { + // Validate start_pos is within bounds + if (start_pos < 0 || start_pos >= len) { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, nullptr, nullptr); + } // Read the next character after RCB. if( !next() ) { - // EOS - add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos ); + int value_len = this->pos - start_pos; + if (value_len >= 0 && value_len <= MAX_VALUE_LENGTH) { + add_key_value_pair( &( this->orig_str[start_pos] ), value_len ); + } break; } @@ -321,11 +409,9 @@ void conn_string_parser:: parse_conn_string( void ) // if second RCB encountered than go back to ValueContent1 if( this->orig_str[pos] == '}' ) { - if( !next() ) { - // EOS after a second RCB is error - THROW_PDO_ERROR( this->ctx, SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, this->current_key_name, NULL ); + THROW_PDO_ERROR( this->ctx, SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, nullptr, nullptr ); } state = ValueContent1; @@ -338,23 +424,27 @@ void conn_string_parser:: parse_conn_string( void ) if( this->is_white_space( this->orig_str[pos] )) { if( ! this->discard_white_spaces() ) { - //EOS - add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos ); + int value_len = end_pos - start_pos; + if (value_len >= 0 && value_len <= MAX_VALUE_LENGTH) { + add_key_value_pair( &( this->orig_str[start_pos] ), value_len ); + } break; } } // if semi-colon than go to next key-value pair if ( this->orig_str[pos] == ';' ) { - - add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos ); + int value_len = end_pos - start_pos; + if (value_len >= 0 && value_len <= MAX_VALUE_LENGTH) { + add_key_value_pair( &( this->orig_str[start_pos] ), value_len ); + } state = NextKeyValuePair; break; } // Non - (RCB, SP*, SC, EOS) character. Any other character after an RCB is an error. - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, this->current_key_name, NULL ); + THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, nullptr, nullptr ); break; } case NextKeyValuePair: @@ -364,19 +454,16 @@ void conn_string_parser:: parse_conn_string( void ) // Call next() to skip the semi-colon. if( !next() || !this->discard_white_spaces() ) { - // EOS break; } if( this->orig_str[pos] == ';' ) { - // a second semi-colon is error case. THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos, NULL ); } else { - // any other character leads to the next key state = Key; break; @@ -386,7 +473,6 @@ void conn_string_parser:: parse_conn_string( void ) } //while } catch( pdo::PDOException& ) { - throw; } } @@ -395,6 +481,12 @@ void conn_string_parser:: parse_conn_string( void ) void sql_string_parser::parse_sql_string( void ) { try { int start_pos = -1; + + // Validate SQL string length + if (len <= 0 || len > MAX_DSN_LENGTH) { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_STRING); + } + while ( !this->is_eos() ) { // if pos is -1, then reading from a string is an initialized read if ( pos == -1 ) { @@ -402,9 +494,14 @@ void sql_string_parser::parse_sql_string( void ) { } // skip until a '"', '\'', ':' or '?' char sym; - while ( this->orig_str[pos] != '"' && this->orig_str[pos] != '\'' && this->orig_str[pos] != ':' && this->orig_str[pos] != '?' && !this->is_eos() ) { + while ( this->orig_str[pos] != '"' && this->orig_str[pos] != '\'' && + this->orig_str[pos] != ':' && this->orig_str[pos] != '?' && + !this->is_eos() ) { next(); } + + if (this->is_eos()) break; + sym = this->orig_str[pos]; // if '"' or '\'', skip until the next '"' or '\'' respectively if ( sym == '"' || sym == '\'' ) { @@ -412,22 +509,30 @@ void sql_string_parser::parse_sql_string( void ) { while ( this->orig_str[pos] != sym && !this->is_eos() ) { next(); } + if (!this->is_eos()) { + next(); // Skip the closing quote + } } // if ':', store string placeholder in the placeholders hashtable else if ( sym == ':' ) { start_pos = this->pos; next(); // keep going until the next space or line break - // while (!is_white_space(this->orig_str[pos]) && !this->is_eos()) { - while ( is_placeholder_char( this->orig_str[pos] )) { + while ( !this->is_eos() && is_placeholder_char( this->orig_str[pos] )) { next(); } - add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos ); - discard_white_spaces(); - // if an '=' is right after a placeholder, it means the placeholder is for output parameters - // and emulate prepare does not support output parameters - if (this->orig_str[pos] == '=') { - THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED); + + // Validate placeholder length + int placeholder_len = this->pos - start_pos; + if (placeholder_len > 0 && placeholder_len <= MAX_KEY_LENGTH) { + add_key_value_pair( &( this->orig_str[start_pos] ), placeholder_len ); + discard_white_spaces(); + + // if an '=' is right after a placeholder, it means the placeholder is for output parameters + // and emulate prepare does not support output parameters + if (!this->is_eos() && this->orig_str[pos] == '=') { + THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED); + } } this->current_key++; } @@ -435,9 +540,9 @@ void sql_string_parser::parse_sql_string( void ) { else if ( sym == '?' ) { next(); // add dummy value to placeholders ht to keep count of the number of placeholders - add_key_int_value_pair( this->current_key ); + add_key_int_value_pair( static_cast(this->current_key) ); discard_white_spaces(); - if (this->orig_str[pos] == '=') { + if (!this->is_eos() && this->orig_str[pos] == '=') { THROW_PDO_ERROR(this->ctx, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED); } this->current_key++; @@ -447,4 +552,4 @@ void sql_string_parser::parse_sql_string( void ) { catch ( pdo::PDOException& ) { throw; } -} \ No newline at end of file +}