MantisBT is Vulnerable to Denial-of-Service (DoS) attack via Excessive Note Length
Description
Mantis Bug Tracker (MantisBT) is an open source issue tracker. Versions 2.27.1 and below allow attackers to permanently corrupt issue activity logs by submitting extremely long notes (tested with 4,788,761 characters) due to a lack of server-side validation of note length. Once such a note is added, the activity stream UI fails to render; therefore, new notes cannot be displayed, effectively breaking all future collaboration on the issue. This issue is fixed in version 2.27.2.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
MantisBT versions ≤2.27.1 allow attackers to permanently corrupt issue activity logs by submitting extremely long notes, breaking collaboration; fixed in 2.27.2.
Vulnerability
Overview
CVE-2025-46556 describes a vulnerability in Mantis Bug Tracker (MantisBT) versions 2.27.1 and below where the application fails to validate the length of bug notes on the server side. An attacker can submit a note containing an extremely large amount of text (tested with 4,788,761 characters) via the bugnote text field. This lack of input validation allows the submission of notes that exceed the database field size or rendering limits, leading to permanent corruption of the issue's activity log [3].
Exploitation
To exploit this vulnerability, an attacker must have the ability to add notes to an issue (typically an authenticated user with appropriate permissions). By crafting a note with an excessive number of characters and submitting it through the bugnote form, the attacker causes the activity stream UI to fail to render. The corruption is permanent because the malformed note is stored in the database and cannot be displayed or removed through the normal interface [3].
Impact
Once a corrupt note is added, the activity stream for that issue becomes unreadable. New notes cannot be displayed, effectively breaking all future collaboration on the affected issue. This denial-of-service condition persists until the note is manually removed from the database, requiring administrative intervention [3].
Mitigation
The vulnerability is fixed in MantisBT version 2.27.2. The fix introduces server-side validation by adding maxlength attributes to textarea fields for bug notes and other text inputs, limiting the input size to prevent excessively long submissions [1][2][4]. Users are advised to upgrade to the latest version to protect their installations.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
mantisbt/mantisbtPackagist | < 2.27.2 | 2.27.2 |
Affected products
2Patches
3e9119c68b4a0Restrict size of textarea fields
31 files changed · +837 −140
account_prof_edit_page.php+3 −1 modified@@ -159,7 +159,9 @@ class="input-sm" size="16" maxlength="16" <?php # Newline after opening textarea tag is intentional, see #25839 ?> <textarea id="description" name="description" class="form-control" - cols="60" rows="8"> + cols="60" rows="8" + maxlength="<?php echo config_get_global( 'max_textarea_length' ) ?>" + > <?php echo string_textarea( $v_description ) ?> </textarea> </td>
account_prof_menu_page.php+3 −1 modified@@ -301,7 +301,9 @@ <td> <textarea id="description" name="description" class="form-control" - cols="80" rows="8"></textarea> + cols="80" rows="8" + maxlength="<?php echo config_get_global( 'max_textarea_length' ) ?>" + ></textarea> </td> </tr> </table>
admin/check/check_customfields_inc.php+240 −33 modified@@ -17,12 +17,16 @@ /** * Custom Fields Checks * @package MantisBT - * @copyright Copyright (C) 2021 MantisBT Team - mantisbt-dev@lists.sourceforge.net + * @copyright Copyright (C) 2021 MantisBT Team - + * mantisbt-dev@lists.sourceforge.net * @link https://mantisbt.org * * @uses check_api.php * @uses config_api.php * @uses constant_inc.php + * + * @noinspection PhpIllegalPsrClassPathInspection + * @noinspection PhpMultipleClassesDeclarationsInOneFile */ if( !defined( 'CHECK_CUSTOMFIELDS_INC_ALLOW' ) ) { @@ -36,42 +40,245 @@ check_print_section_header_row( 'Custom Fields' ); -# Check for deprecated usage of {} in Date CF default values -$t_date_cf_with_bracket = array(); -foreach( custom_field_get_ids() as $t_id ) { +$t_checks = new CustomFieldsChecks(); +$t_checks->register( new CheckDateDefaultWithBrackets ); +$t_checks->register( new CheckTextareaMaxLength ); +$t_checks->execute(); + +/** + * Custom Fields checks controller class. + * + * Register individual checks with register() then call execute(). + */ +class CustomFieldsChecks { + /** - * @var int $v_id - * @var string $v_name - * @var int $v_type - * @var string|int $v_default_value + * @var array Custom Field definitions. */ - extract( custom_field_get_definition( $t_id ), EXTR_PREFIX_ALL, 'v'); + private array $definitions; + + /** + * @var CustomFieldCheck[] Registered checks. + */ + private array $checks = []; + + + public function __construct() { + foreach( custom_field_get_ids() as $t_id ) { + $this->definitions[$t_id] = custom_field_get_definition( $t_id ); + } + } - if( $v_type != CUSTOM_FIELD_TYPE_DATE ) { - continue; + public function register( CustomFieldCheck $check ) { + $this->checks[] = $check; } - if( preg_match( '/^{(.*)}$/', $v_default_value, $t_matches ) ) { - $t_date_cf_with_bracket[$v_name] = array( $v_id, $t_matches[1] ); + + public function execute() { + foreach( $this->definitions as $t_cfdef ) { + foreach( $this->checks as $t_check ) { + $t_check->execute( $t_cfdef ); + } + } + + foreach( $this->checks as $t_check ) { + $t_check->printResults(); + } } } -if( $t_date_cf_with_bracket ) { - $t_manage_cf_link = '<a href="' - . helper_mantis_url( 'manage_custom_field_edit_page.php' ) - . '?field_id=%d">Edit the Custom Field</a>'; - ksort( $t_date_cf_with_bracket ); - foreach( $t_date_cf_with_bracket as $t_name => list( $t_id, $t_new_value ) ) { - check_print_test_warn_row( - "Date Custom Field '$t_name' specifies its Default Value with deprecated curly brackets format.", - false, - array( false => "Use the same format, but without the '{}', i.e. '$t_new_value'. " - . sprintf( $t_manage_cf_link, $t_id ) - ) - ); - } -} else { - check_print_test_warn_row( - 'Deprecated usage of curly brackets in Date Custom Fields default value', - true, - '' - ); + +/** + * Abstract base class for Custom Field definition checks. + * + * Child classes must: + * - define the test() method + * - override $msg_* properties and related get methods as appropriate + * + * @todo child class should be allowed to select warning or error checks. + * currently, we only call check_print_test_warn_row(). + */ +abstract class CustomFieldCheck { + + /** + * Message to print if the check passes. + */ + protected string $msg_pass; + + /** + * Failed check description. + */ + protected string $msg_fail = ''; + + /** + * Failed check additional info. + */ + protected string $msg_info = ''; + + /** + * Link to edit Custom Field (use sprintf to insert CF id). + */ + protected string $msg_edit_cf_link; + + /** + * @var array CFname => (id, additional_fields...) + */ + protected array $results = []; + + public function __construct() { + $t_cf_edit_page = helper_mantis_url( 'manage_custom_field_edit_page.php' ); + $this->msg_edit_cf_link = '<a href="' . $t_cf_edit_page .'?field_id=%d">Edit the Custom Field</a>'; + } + + /** + * Test to determine whether the check passes or not. + * + * @param array $p_cfdef + * @param string|null $p_result Additional info about the test, for the message + * display. + * + * @return bool True if pass. + */ + abstract public function test( array $p_cfdef, ?string &$p_result ): bool; + + /** + * Executes the check and store the result. + * + * @param array $p_cfdef Custom Field definition + * @return void + */ + public function execute( array $p_cfdef ) { + if( !$this->test( $p_cfdef, $t_result ) ) { + $this->results[$p_cfdef['name']] = array( $p_cfdef['id'], $t_result ); + } + } + + /** + * Prints the check result + * @return void + */ + public function printResults() { + if( $this->results ) { + ksort( $this->results ); + foreach( array_keys( $this->results ) as $t_name ) { + check_print_test_warn_row( + $this->getFailMessage( $t_name ), + false, + $this->getInfoMessage( $t_name ) . $this->getEditLink( $t_name ) + ); + } + } else { + check_print_test_warn_row( $this->getPassMessage(), true ); + } + } + + public function getPassMessage(): string { + return $this->msg_pass; + } + + public function getFailMessage(string $p_name): string { + return $this->msg_fail; + } + + public function getInfoMessage(string $p_name): string { + return $this->msg_info; + } + + public function getEditLink(string $p_name): string { + return sprintf( $this->msg_edit_cf_link, $this->results[$p_name][0] ); + } + +} + +/** + * Checks for usage of curly brackets in Date Custom Fields default value. + */ +class CheckDateDefaultWithBrackets extends CustomFieldCheck +{ + protected string $msg_pass = 'Deprecated usage of curly brackets in Date Custom Fields default value'; + protected string $msg_fail = "Date Custom Field '%s' specifies its Default Value with deprecated curly brackets format."; + protected string $msg_info = "Use the same format, but without the '{}', i.e. '%s'. "; + + /** + * @param array $p_cfdef + * @param string|null $p_result Default value + * @return bool + */ + public function test( array $p_cfdef, ?string &$p_result ): bool { + /** + * @var int $v_type + * @var string|int $v_default_value + */ + extract( $p_cfdef, EXTR_PREFIX_ALL, 'v'); + + if( $v_type == CUSTOM_FIELD_TYPE_DATE + && preg_match( '/^{(.*)}$/', $v_default_value, $t_matches ) + ) { + $p_result = $t_matches[1]; + return false; + } + return true; + } + + public function getFailMessage(string $p_name): string { + return sprintf( $this->msg_fail, $p_name ); + } + + public function getInfoMessage(string $p_name): string { + return sprintf( $this->msg_info, $this->results[$p_name][1] ); + } + +} + +/** + * Checks if Textarea Custom Fields maximum length and default value are + * bigger than $g_max_textarea_length. + */ +class CheckTextareaMaxLength extends CustomFieldCheck +{ + protected string $msg_pass = 'Maximum length and Default value of Textarea Custom Fields ' + . 'are smaller than $g_max_textarea_length'; + + protected int $max_textarea_length; + + public function __construct() { + parent::__construct(); + $this->max_textarea_length = config_get_global( 'max_textarea_length' ); + $this->msg_fail = 'Textarea Custom Field "%s": %s bigger than $g_max_textarea_length' + . " ($this->max_textarea_length)"; + } + + /** + * @param array $p_cfdef + * @param string|null $p_result List of fields that are too long. + * @return bool + */ + public function test( array $p_cfdef, ?string &$p_result ): bool { + /** + * @var string $v_name + * @var int $v_type + * @var int $v_length_max + * @var string $v_default_value + */ + extract( $p_cfdef, EXTR_PREFIX_ALL, 'v'); + + if( $v_type == CUSTOM_FIELD_TYPE_TEXTAREA ) { + $t_fields = []; + if( $v_length_max > $this->max_textarea_length ) { + $t_fields[] = 'Maximum length'; + } + if( strlen( $v_default_value) > $this->max_textarea_length ) { + $t_fields[] = 'Default value'; + } + if( $t_fields ) { + $p_result = implode( ' and ', $t_fields ) + . ( count( $t_fields ) == 1 ? ' is' : ' are' ); + return false; + } + } + return true; + } + + public function getFailMessage(string $p_name): string { + return sprintf( $this->msg_fail, $p_name, $this->results[$p_name][1] ); + } + }
admin/check/index.php+68 −68 modified@@ -117,86 +117,86 @@ function mode_url( $p_all, $p_errors ) { <?php define( 'CHECK_PHP_INC_ALLOW', true ); -include( 'check_php_inc.php' ); +//include( 'check_php_inc.php' ); if( !$g_failed_test ) { define( 'CHECK_DATABASE_INC_ALLOW', true ); include( 'check_database_inc.php' ); } -if( !$g_failed_test ) { - define( 'CHECK_CONFIG_INC_ALLOW', true ); - include( 'check_config_inc.php' ); -} - -if( !$g_failed_test ) { - define( 'CHECK_PATHS_INC_ALLOW', true ); - include( 'check_paths_inc.php' ); -} - -if( !$g_failed_test ) { - define( 'CHECK_WEBSERVICE_INC_ALLOW', true ); - include( 'check_webservice_inc.php' ); -} - -/* - * Disable integrity since the required blobs are no longer available - * See https://sourceforge.net/p/mantisbt/mailman/message/24608409/ - * -if( !$g_failed_test ) { - define( 'CHECK_INTEGRITY_INC_ALLOW', true ); - include( 'check_integrity_inc.php' ); -} -*/ - -if( !$g_failed_test ) { - define( 'CHECK_CRYPTO_INC_ALLOW', true ); - include( 'check_crypto_inc.php' ); -} - -if( !$g_failed_test ) { - define( 'CHECK_I18N_INC_ALLOW', true ); - include( 'check_i18n_inc.php' ); -} - -if( !$g_failed_test ) { - define( 'CHECK_L10N_INC_ALLOW', true ); - include( 'check_L10n_inc.php' ); -} - -# @TODO $t_email_failed_test is a temp workaround to be removed when fixing #33012 -$t_email_failed_test = false; -if( !$g_failed_test ) { - define( 'CHECK_EMAIL_INC_ALLOW', true ); - include( 'check_email_inc.php' ); - $t_email_failed_test = $g_failed_test; - $g_failed_test = false; -} - -if( !$g_failed_test ) { - define( 'CHECK_ANONYMOUS_INC_ALLOW', true ); - include( 'check_anonymous_inc.php' ); -} - -if( !$g_failed_test ) { - define( 'CHECK_ATTACHMENTS_INC_ALLOW', true ); - include( 'check_attachments_inc.php' ); -} - -if( !$g_failed_test ) { - define( 'CHECK_DISPLAY_INC_ALLOW', true ); - include( 'check_display_inc.php' ); -} +//if( !$g_failed_test ) { +// define( 'CHECK_CONFIG_INC_ALLOW', true ); +// include( 'check_config_inc.php' ); +//} +// +//if( !$g_failed_test ) { +// define( 'CHECK_PATHS_INC_ALLOW', true ); +// include( 'check_paths_inc.php' ); +//} +// +//if( !$g_failed_test ) { +// define( 'CHECK_WEBSERVICE_INC_ALLOW', true ); +// include( 'check_webservice_inc.php' ); +//} +// +///* +// * Disable integrity since the required blobs are no longer available +// * See https://sourceforge.net/p/mantisbt/mailman/message/24608409/ +// * +//if( !$g_failed_test ) { +// define( 'CHECK_INTEGRITY_INC_ALLOW', true ); +// include( 'check_integrity_inc.php' ); +//} +//*/ +// +//if( !$g_failed_test ) { +// define( 'CHECK_CRYPTO_INC_ALLOW', true ); +// include( 'check_crypto_inc.php' ); +//} +// +//if( !$g_failed_test ) { +// define( 'CHECK_I18N_INC_ALLOW', true ); +// include( 'check_i18n_inc.php' ); +//} +// +//if( !$g_failed_test ) { +// define( 'CHECK_L10N_INC_ALLOW', true ); +// include( 'check_L10n_inc.php' ); +//} +// +//# @TODO $t_email_failed_test is a temp workaround to be removed when fixing #33012 +//$t_email_failed_test = false; +//if( !$g_failed_test ) { +// define( 'CHECK_EMAIL_INC_ALLOW', true ); +// include( 'check_email_inc.php' ); +// $t_email_failed_test = $g_failed_test; +// $g_failed_test = false; +//} +// +//if( !$g_failed_test ) { +// define( 'CHECK_ANONYMOUS_INC_ALLOW', true ); +// include( 'check_anonymous_inc.php' ); +//} +// +//if( !$g_failed_test ) { +// define( 'CHECK_ATTACHMENTS_INC_ALLOW', true ); +// include( 'check_attachments_inc.php' ); +//} +// +//if( !$g_failed_test ) { +// define( 'CHECK_DISPLAY_INC_ALLOW', true ); +// include( 'check_display_inc.php' ); +//} if( !$g_failed_test ) { define( 'CHECK_CUSTOMFIELDS_INC_ALLOW', true ); include( 'check_customfields_inc.php' ); } -if( !$g_failed_test ) { - define( 'CHECK_PLUGINS_INC_ALLOW', true ); - include( 'check_plugins_inc.php' ); -} +//if( !$g_failed_test ) { +// define( 'CHECK_PLUGINS_INC_ALLOW', true ); +// include( 'check_plugins_inc.php' ); +//} ?> </table> </div>
bug_actiongroup_add_note_inc.php+4 −1 modified@@ -75,7 +75,10 @@ function action_add_note_print_fields() { <?php echo lang_get( 'add_bugnote_title' ); ?> </th> <td> - <textarea class="form-control" name="bugnote_text" id="bugnote_text" cols="80" rows="10"></textarea> + <textarea class="form-control" name="bugnote_text" id="bugnote_text" + cols="80" rows="10" + maxlength="<?php echo config_get_global( 'max_textarea_length' ) ?>" + ></textarea> </td> </tr>
bug_actiongroup_page.php+4 −1 modified@@ -415,7 +415,10 @@ <?php echo lang_get( 'add_bugnote_title' ); ?> </th> <td> - <textarea name="bugnote_text" id="bugnote_text" class="<?php echo $t_bugnote_class ?>" cols="80" rows="7"></textarea> + <textarea name="bugnote_text" id="bugnote_text" class="<?php echo $t_bugnote_class ?>" + cols="80" rows="7" + maxlength="<?php echo config_get_global( 'max_textarea_length' ) ?>" + ></textarea> </td> </tr> <?php
bug_change_status_page.php+5 −1 modified@@ -380,7 +380,11 @@ <?php echo lang_get( 'add_bugnote_title' ) ?> </th> <td> - <textarea name="bugnote_text" id="bugnote_text" class="<?php echo $t_bugnote_class ?>" cols="80" rows="7"></textarea> + <textarea name="bugnote_text" id="bugnote_text" + class="<?php echo $t_bugnote_class ?>" + cols="80" rows="7" + maxlength="<?php echo config_get_global( 'max_textarea_length' ) ?>" + ></textarea> </td> </tr> <?php
bugnote_add_inc.php+4 −1 modified@@ -116,7 +116,10 @@ <?php echo lang_get( 'bugnote' ) ?> </th> <td class="width-85"> - <textarea name="bugnote_text" id="bugnote_text" class="<?php echo $t_bugnote_class ?>" rows="7"></textarea> + <textarea name="bugnote_text" id="bugnote_text" class="<?php echo $t_bugnote_class ?>" + rows="7" + maxlength="<?php echo config_get_global( 'max_textarea_length' ) ?>" + ></textarea> </td> </tr>
bugnote_edit_page.php+4 −2 modified@@ -130,8 +130,10 @@ <table class="table table-bordered table-condensed table-striped"> <tr> <td colspan="2"> - <textarea class="form-control <?php echo $t_bugnote_class; ?>" cols="80" rows="10" name="bugnote_text" - id="bugnote_text"><?php echo $t_bugnote_text ?></textarea> + <textarea id="bugnote_text" name="bugnote_text" class="form-control <?php echo $t_bugnote_class; ?>" + cols="80" rows="10" + maxlength="<?php echo config_get_global( 'max_textarea_length' ) ?>" + ><?php echo $t_bugnote_text ?></textarea> </td> </tr> <?php if( config_get( 'time_tracking_enabled' ) ) { ?>
bugnote_update.php+1 −1 modified@@ -80,7 +80,7 @@ trigger_error( ERROR_BUG_READ_ONLY_ACTION_DENIED, ERROR ); } -$f_bugnote_text = trim( $f_bugnote_text ) . "\n"; +$f_bugnote_text = trim( $f_bugnote_text ); bugnote_set_text( $f_bugnote_id, $f_bugnote_text ); bugnote_set_time_tracking( $f_bugnote_id, $f_time_tracking );
bug_reminder_page.php+4 −2 modified@@ -144,8 +144,10 @@ </select> </td> <td> - <textarea name="bugnote_text" cols="65" rows="10" - class="form-control <?php echo $t_bugnote_class; ?>"></textarea> + <textarea name="bugnote_text" class="form-control <?php echo $t_bugnote_class; ?>" + cols="65" rows="10" + maxlength="<?php echo config_get_global( 'max_textarea_length' ) ?>" + ></textarea> </td> </tr> </table>
bug_report_page.php+16 −3 modified@@ -226,6 +226,7 @@ $t_show_due_date = in_array( 'due_date', $t_fields ) && access_has_project_level( config_get( 'due_date_update_threshold' ), helper_get_current_project(), auth_get_current_user_id() ); $t_show_attachments = in_array( 'attachments', $t_fields ) && file_allow_bug_upload(); $t_show_view_state = in_array( 'view_state', $t_fields ) && access_has_project_level( config_get( 'set_view_status_threshold' ) ); +$t_max_length = config_get_global( 'max_textarea_length' ); # don't index bug report page html_robots_noindex(); @@ -573,7 +574,11 @@ </th> <td> <?php # Newline after opening textarea tag is intentional, see #25839 ?> - <textarea class="form-control" <?php echo helper_get_tab_index() ?> id="description" name="description" cols="80" rows="10" required> + <textarea id="description" name="description" required + class="form-control" <?php echo helper_get_tab_index() ?> + cols="80" rows="10" + maxlength="<?php echo $t_max_length ?>" + > <?php echo string_textarea( $f_description ) ?> </textarea> </td> @@ -586,7 +591,11 @@ </th> <td> <?php # Newline after opening textarea tag is intentional, see #25839 ?> - <textarea class="form-control" <?php echo helper_get_tab_index() ?> id="steps_to_reproduce" name="steps_to_reproduce" cols="80" rows="10"> + <textarea id="steps_to_reproduce" name="steps_to_reproduce" + class="form-control" <?php echo helper_get_tab_index() ?> + cols="80" rows="10" + maxlength="<?php echo $t_max_length ?>" + > <?php echo string_textarea( $f_steps_to_reproduce ) ?> </textarea> </td> @@ -600,7 +609,11 @@ </th> <td> <?php # Newline after opening textarea tag is intentional, see #25839 ?> - <textarea class="form-control" <?php echo helper_get_tab_index() ?> id="additional_info" name="additional_info" cols="80" rows="10"> + <textarea id="additional_info" name="additional_info" + class="form-control" <?php echo helper_get_tab_index() ?> + cols="80" rows="10" + maxlength="<?php echo $t_max_length ?>" + > <?php echo string_textarea( $f_additional_info ) ?> </textarea> </td>
bug_update_page.php+16 −4 modified@@ -95,6 +95,7 @@ $t_bug_id = $f_bug_id; $t_action_button_position = config_get( 'action_button_position' ); +$t_max_textarea_length = config_get_global( 'max_textarea_length' ); $t_top_buttons_enabled = $t_action_button_position == POSITION_TOP || $t_action_button_position == POSITION_BOTH; $t_bottom_buttons_enabled = $t_action_button_position == POSITION_BOTTOM || $t_action_button_position == POSITION_BOTH; @@ -665,7 +666,9 @@ echo '</th>'; echo '<td colspan="5">'; echo '<textarea class="form-control" required ', helper_get_tab_index(), - ' cols="80" rows="10" id="description" name="description">', "\n", + ' cols="80" rows="10"', + ' maxlength="' . $t_max_textarea_length . '"', + ' id="description" name="description">', "\n", $t_description_textarea, '</textarea>'; echo '</td></tr>'; } @@ -676,7 +679,9 @@ echo '<th class="category"><label for="steps_to_reproduce">' . lang_get( 'steps_to_reproduce' ) . '</label></th>'; echo '<td colspan="5">'; echo '<textarea class="form-control" ', helper_get_tab_index(), - ' cols="80" rows="10" id="steps_to_reproduce" name="steps_to_reproduce">', "\n", + ' cols="80" rows="10"', + ' maxlength="' . $t_max_textarea_length . '"', + ' id="steps_to_reproduce" name="steps_to_reproduce">', "\n", $t_steps_to_reproduce_textarea, '</textarea>'; echo '</td></tr>'; } @@ -687,7 +692,9 @@ echo '<th class="category"><label for="additional_information">' . lang_get( 'additional_information' ) . '</label></th>'; echo '<td colspan="5">'; echo '<textarea class="form-control" ', helper_get_tab_index(), - ' cols="80" rows="10" id="additional_information" name="additional_information">', "\n", + ' cols="80" rows="10"', + ' maxlength="' . $t_max_textarea_length . '"', + ' id="additional_information" name="additional_information">', "\n", $t_additional_information_textarea, '</textarea>'; echo '</td></tr>'; } @@ -736,7 +743,12 @@ echo '<tr>'; echo '<th class="category"><label for="bugnote_text">' . lang_get( 'add_bugnote_title' ) . '</label></th>'; -echo '<td colspan="5"><textarea ', helper_get_tab_index(), ' id="bugnote_text" name="bugnote_text" class="', $t_bugnote_class, '" cols="80" rows="7"></textarea></td></tr>'; +echo '<td colspan="5"><textarea ', helper_get_tab_index(), + ' id="bugnote_text" name="bugnote_text" class="', $t_bugnote_class, + '" cols="80" rows="7"', + ' maxlength="' . $t_max_textarea_length . '">', + '</textarea></td></tr>'; + # Bugnote Private Checkbox (if permitted) if( access_has_bug_level( config_get( 'private_bugnote_threshold' ), $t_bug_id ) ) {
config_defaults_inc.php+14 −0 modified@@ -2373,6 +2373,18 @@ */ $g_html_valid_tags_single_line = 'i, b, u, em, strong'; +/** + * Maximum size for long text fields. + * + * Applies to: bug description, steps to reproduce, additional information, + * bugnotes. + * + * This reduces the risk of DoS attacks (see #35893). + * + * @global int $g_max_textarea_length + */ +$g_max_textarea_length = 65535; + /** * Maximum length of the description in a dropdown menu (for search). * @@ -5235,6 +5247,7 @@ 'long_process_timeout', 'manage_config_cookie', 'manual_url', + 'max_textarea_length', 'path', 'plugin_path', 'plugins_enabled', @@ -5478,6 +5491,7 @@ 'max_failed_login_count', 'max_file_size', 'max_lost_password_in_progress_count', + 'max_textarea_length', 'mentions_enabled', 'mentions_tag', 'min_refresh_delay',
core/bug_api.php+4 −0 modified@@ -496,6 +496,10 @@ public function validate( $p_update_extended = true ) { error_parameters( lang_get( 'description' ) ); trigger_error( ERROR_EMPTY_FIELD, ERROR ); } + + helper_ensure_longtext_length_valid( $this->description, 'description' ); + helper_ensure_longtext_length_valid( $this->steps_to_reproduce, 'steps_to_reproduce' ); + helper_ensure_longtext_length_valid( $this->additional_information, 'additional_information' ); } # Make sure a category is set
core/bugnote_api.php+4 −1 modified@@ -267,6 +267,8 @@ function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_ # Event integration $t_bugnote_text = event_signal( 'EVENT_BUGNOTE_DATA', $p_bugnote_text, $c_bug_id ); + helper_ensure_longtext_length_valid( $t_bugnote_text, 'bugnote' ); + # MySQL 4-bytes UTF-8 chars workaround #21101 $t_bugnote_text = db_mysql_fix_utf8( $t_bugnote_text ); @@ -734,10 +736,11 @@ function bugnote_set_text( $p_bugnote_id, $p_bugnote_text ) { if( $t_old_text == $p_bugnote_text ) { return true; } + helper_ensure_longtext_length_valid( $p_bugnote_text, 'bugnote' ); + # MySQL 4-bytes UTF-8 chars workaround #21101 $p_bugnote_text = db_mysql_fix_utf8( $p_bugnote_text ); - $t_bug_id = bugnote_get_field( $p_bugnote_id, 'bug_id' ); $t_bugnote_text_id = bugnote_get_field( $p_bugnote_id, 'bugnote_text_id' );
core/cfdefs/cfdef_standard.php+5 −3 modified@@ -490,9 +490,11 @@ function cfdef_input_textbox( array $p_field_def, $p_custom_field_value, $p_requ function cfdef_input_textarea( array $p_field_def, $p_custom_field_value, $p_required = '' ) { echo '<textarea class="form-control" ', helper_get_tab_index(), ' id="custom_field_' . $p_field_def['id'] , '" name="custom_field_', $p_field_def['id'], '"', $p_required; - if( $p_field_def['length_max'] > 0 ) { - echo ' maxlength="', $p_field_def['length_max'], '"'; - } + echo ' maxlength="', + $p_field_def['length_max'] > 0 + ? $p_field_def['length_max'] + : config_get_global( 'max_textarea_length' ), + '"'; echo ' cols="70" rows="8">', $p_custom_field_value, '</textarea>'; }
core/commands/IssueAddCommand.php+7 −2 modified@@ -116,7 +116,6 @@ protected function validate() { ERROR_EMPTY_FIELD, array( 'summary' ) ); } - $t_summary = $t_issue['summary']; if( !isset( $t_issue['description'] ) || is_blank( $t_issue['description'] ) ) { @@ -125,8 +124,14 @@ protected function validate() { ERROR_EMPTY_FIELD, array( 'description' ) ); } - $t_description = $t_issue['description']; + helper_ensure_longtext_length_valid( $t_description, 'description' ); + + $t_steps_to_reproduce = $t_issue['steps_to_reproduce'] ?? ''; + helper_ensure_longtext_length_valid( $t_steps_to_reproduce, 'steps_to_reproduce' ); + + $t_additional_information = $t_issue['additional_information'] ?? ''; + helper_ensure_longtext_length_valid( $t_additional_information, 'additional_information' ); if( !isset( $t_issue['project'] ) ) { throw new ClientException(
core/constant_inc.php+1 −0 modified@@ -626,6 +626,7 @@ define( 'DB_FIELD_SIZE_API_TOKEN_NAME', 128 ); define( 'DB_FIELD_SIZE_HISTORY_VALUE', 255 ); define( 'DB_FIELD_SIZE_FILENAME', 250 ); +define( 'DB_FIELD_SIZE_CF_DEFAULT_VALUE', 255 ); # Maximum size for the user's password when storing it as a hash define( 'PASSWORD_MAX_SIZE_BEFORE_HASH', 1024 );
core/custom_field_api.php+30 −0 modified@@ -626,6 +626,22 @@ function custom_field_update( $p_field_id, array $p_def_array ) { trigger_error( ERROR_EMPTY_FIELD, ERROR ); } + # Default value length is limited by underlying DB field + # (and max_textarea_length, for textarea fields). + if( $v_type == CUSTOM_FIELD_TYPE_TEXTAREA ) { + $t_max_textarea_length = config_get_global( 'max_textarea_length' ); + $t_max_length = min( DB_FIELD_SIZE_CF_DEFAULT_VALUE, $t_max_textarea_length ); + } else { + $t_max_length = DB_FIELD_SIZE_CF_DEFAULT_VALUE; + } + if( strlen( $v_default_value ) > $t_max_length ) { + throw new ClientException( + "Default value must be $t_max_length characters or less.", + ERROR_FIELD_TOO_LONG, + [lang_get( 'custom_field_default_value' ), $t_max_length] + ); + } + if( $v_access_level_rw < $v_access_level_r ) { error_parameters( lang_get( 'custom_field_access_level_r' ) . ', ' . @@ -640,6 +656,17 @@ function custom_field_update( $p_field_id, array $p_def_array ) { trigger_error( ERROR_CUSTOM_FIELD_INVALID_PROPERTY, ERROR ); } + # Ensure max length for textarea fields is not more than allowed + if( $v_type == CUSTOM_FIELD_TYPE_TEXTAREA ) { + if( $v_length_max > $t_max_textarea_length ) { + throw new ClientException( + 'Maximum Length must be ' . $t_max_textarea_length . ' or less.', + ERROR_FIELD_TOO_LONG, + [lang_get( 'custom_field_length_max' ), $t_max_textarea_length] + ); + } + } + if( !custom_field_is_name_unique( $v_name, $p_field_id ) ) { trigger_error( ERROR_CUSTOM_FIELD_NAME_NOT_UNIQUE, ERROR ); } @@ -1223,6 +1250,9 @@ function custom_field_validate( $p_field_id, $p_value ) { # Check the length of the string $t_valid &= ( 0 == $t_length_min ) || ( $t_length >= $t_length_min ); $t_valid &= ( 0 == $t_length_max ) || ( $t_length <= $t_length_max ); + if( $t_type == CUSTOM_FIELD_TYPE_TEXTAREA ) { + $t_valid &= $t_length <= config_get_global( 'max_textarea_length' ); + } break; case CUSTOM_FIELD_TYPE_NUMERIC: # Empty fields are valid
core/helper_api.php+32 −1 modified@@ -904,4 +904,35 @@ function helper_get_link_attributes( $p_return_array = true ) { $t_string .= " $t_attr=\"$t_value\""; } return $t_string; -} \ No newline at end of file +} + +/** + * Checks that a string's length is within the allowed size. + * + * @param string $p_string Text to check + * + * @return bool True if smaller than or equal to the maximum allowed length + * {@see $g_max_textarea_length}. + */ +function helper_is_longtext_length_valid( string $p_string ): bool { + return mb_strlen( $p_string ) <= config_get_global( 'max_textarea_length' ); +} + +/** + * Throws error if a string's length is bigger than the allowed maximum. + * + * @param string $p_string Text to check. + * @param string $p_field Field name. + * + * @throws ClientException + */ +function helper_ensure_longtext_length_valid( string $p_string, string $p_field ): void { + if( !helper_is_longtext_length_valid( $p_string ) ) { + $t_max_length = config_get_global( 'max_textarea_length' ); + throw new ClientException( + 'Long text field "' . $p_field . '" must be shorter than ' . $t_max_length . ' characters.', + ERROR_FIELD_TOO_LONG, + array( lang_get( $p_field ), $t_max_length ) + ); + } +}
core/profile_api.php+11 −7 modified@@ -59,7 +59,7 @@ function profile_create( $p_user_id, $p_platform, $p_os, $p_os_build, $p_description ) { $p_user_id = (int)$p_user_id; - profile_validate_before_update( $p_user_id, $p_platform, $p_os, $p_os_build ); + profile_validate_before_update( $p_user_id, $p_platform, $p_os, $p_os_build, $p_description ); # Add profile $t_query = new DbQuery(); @@ -118,7 +118,7 @@ function profile_delete( $p_user_id, $p_profile_id ) { * @throws ClientException if user is protected */ function profile_update( $p_user_id, $p_profile_id, $p_platform, $p_os, $p_os_build, $p_description ) { - profile_validate_before_update( $p_user_id, $p_platform, $p_os, $p_os_build ); + profile_validate_before_update( $p_user_id, $p_platform, $p_os, $p_os_build, $p_description ); # Update profile $t_query = new DbQuery(); @@ -141,15 +141,16 @@ function profile_update( $p_user_id, $p_profile_id, $p_platform, $p_os, $p_os_bu /** * Validates that the given profile data is valid, throw errors if not. * - * @param int $p_user_id A valid user identifier. - * @param string $p_platform Value for profile platform. - * @param string $p_os Value for profile operating system. - * @param string $p_os_build Value for profile operation system build. + * @param int $p_user_id A valid user identifier. + * @param string $p_platform Value for profile platform. + * @param string $p_os Value for profile operating system. + * @param string $p_os_build Value for profile operation system build. + * @param string $p_description Value for profile description. * * @throws ClientException * @internal */ -function profile_validate_before_update( $p_user_id, $p_platform, $p_os, $p_os_build ) { +function profile_validate_before_update( $p_user_id, $p_platform, $p_os, $p_os_build, $p_description ) { if( ALL_USERS != $p_user_id ) { user_ensure_unprotected( $p_user_id ); } @@ -171,6 +172,9 @@ function profile_validate_before_update( $p_user_id, $p_platform, $p_os, $p_os_b error_parameters( lang_get( 'version' ) ); trigger_error( ERROR_EMPTY_FIELD, ERROR ); } + + # Description length + helper_ensure_longtext_length_valid( $p_description, 'profile_description' ); } /**
docbook/Admin_Guide/en-US/config/html.xml+12 −0 modified@@ -203,6 +203,18 @@ $g_main_menu_custom_options = array( set to 0 to disable truncations</para> </listitem> </varlistentry> + <varlistentry> + <term>$g_max_textarea_length</term> + <listitem> + <para>Maximum size for long text fields. Applies to: + bug description, steps to reproduce, additional information, + bugnotes. + </para> + <para>Reduces the risk of Denial-of-Service (DoS) attacks + (see Issue <ulink url="https://mantisbt.org/bugs/view.php?id=35893">35893</ulink>). + </para> + </listitem> + </varlistentry> <varlistentry> <term>$g_wrap_in_preformatted_text</term> <listitem>
lang/strings_english.txt+1 −1 modified@@ -1761,7 +1761,7 @@ $MANTIS_ERROR[ERROR_DISPLAY_USER_ERROR_INLINE] = 'Warning: The system is configu $MANTIS_ERROR[ERROR_TYPE_MISMATCH] = 'Data Type mismatch. Enable detailed error messages for further information.'; $MANTIS_ERROR[ERROR_BUG_CONFLICTING_EDIT] = 'This issue has been updated by another user, please return to the issue and submit your changes again.'; $MANTIS_ERROR[ERROR_SPAM_SUSPECTED] = 'You have reached the allowed activity limit of %d events within the last %d seconds; your action has been blocked to avoid spam, please try again later.'; -$MANTIS_ERROR[ERROR_FIELD_TOO_LONG] = 'Field "%1$s" must be shorter or equal to %2$d characters long.'; +$MANTIS_ERROR[ERROR_FIELD_TOO_LONG] = 'Maximum length for Field "%1$s" is %2$d characters.'; $MANTIS_ERROR[ERROR_API_TOKEN_NAME_NOT_UNIQUE] = 'API token name "%s" is already being used. Please go back and select another one.'; $MANTIS_ERROR[ERROR_LOGFILE_NOT_WRITABLE] = 'The file specified in $g_log_destination "%s" is not writable.'; $MANTIS_ERROR[ERROR_MONITOR_ACCESS_TOO_LOW] = 'Added user does not have sufficient access rights to monitor this issue.';
manage_custom_field_edit_page.php+3 −2 modified@@ -148,8 +148,9 @@ class="input-sm" size="32" maxlength="255" <div class="textarea"> <?php # Newline after opening textarea tag is intentional, see #25839 ?> <textarea id="custom-field-default-value-textarea" name="default_value" - class="form-control" cols="80" rows="10" disabled="disabled"> -<?php echo string_attribute( $t_definition['default_value'] ) ?> + class="form-control" cols="80" rows="10" maxlength="255" + disabled="disabled"> +<?php echo string_textarea( $t_definition['default_value'] ) ?> </textarea> </div> </td>
tests/rest/RestBase.php+11 −0 modified@@ -223,6 +223,17 @@ protected function skipTestIfAnonymousDisabled(){ } } + /** + * Skip if time tracking is not enabled + * @return void + */ + protected function skipIfTimeTrackingIsNotEnabled() { + $t_time_tracking_enabled = config_get( 'time_tracking_enabled' ); + if( !$t_time_tracking_enabled ) { + $this->markTestSkipped( 'Time tracking is not enabled' ); + } + } + /** * Registers an issue for deletion after the test method has run *
tests/rest/RestIssueNotesTest.php+245 −0 added@@ -0,0 +1,245 @@ +<?php +# MantisBT - A PHP based bugtracking system + +# MantisBT is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# MantisBT is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with MantisBT. If not, see <http://www.gnu.org/licenses/>. + +/** + * Mantis Webservice Tests + * + * @package Tests + * @subpackage UnitTests + * @copyright Copyright MantisBT Team - mantisbt-dev@lists.sourceforge.net + * @link http://www.mantisbt.org + */ + +namespace Mantis\tests\rest; + +use Psr\Http\Message\ResponseInterface; +use RestBase; +use function PHPUnit\Framework\assertEquals; + +require_once 'RestBase.php'; + +/** + * Test fixture for Issue notes webservice methods. + * + * @TODO This is just a minimal suite covering adding plain notes + * + * @requires extension curl + * @group REST + */ +class RestIssueNotesTest extends RestBase +{ + /** + * @var int $issueId; + */ + private int $issueId; + + /** + * Test for adding plain, public notes via POST /issue/{id}/notes + * + * Covers empty payload and note text and maximum length cases. + * + * @return void + */ + public function testAddNote() { + $this->createTestIssue(); + + # Plain bug note + $t_response = $this->addNote( $this->generateNoteData( 'Test Note' ) ); + $this->assertEquals( HTTP_STATUS_CREATED, $t_response->getStatusCode(), + "Creating a note should succeed" + ); + + # Maximum length + $t_long_text = str_repeat( 'x', config_get_global( 'max_textarea_length' ) ); + $t_response = $this->addNote( $this->generateNoteData( $t_long_text ) ); + $this->assertEquals( HTTP_STATUS_CREATED, $t_response->getStatusCode(), + "Creating a note with maximum size should succeed" + ); + + # Too long + $t_response = $this->addNote( $this->generateNoteData( $t_long_text . ' TOO LONG' ) ); + $this->assertEquals( HTTP_STATUS_BAD_REQUEST, $t_response->getStatusCode(), + "Creating a note longer than max size should fail" + ); + + # Empty note text and payload + $t_response = $this->addNote( [] ); + $this->assertEquals( HTTP_STATUS_BAD_REQUEST, $t_response->getStatusCode(), + "Creating a note with an empty payload should fail" + ); + $t_response = $this->addNote( $this->generateNoteData( '' ) ); + $this->assertEquals( HTTP_STATUS_BAD_REQUEST, $t_response->getStatusCode(), + "Creating a empty note should fail" + ); + } + + /** + * Test adding notes to a non-existing Issue. + * + * @return void + */ + public function testAddNoteToNonExistingIssue() { + $this->issueId = 99999999; + $t_response = $this->addNote( $this->generateNoteData( 'Test Note' ) ); + $this->assertEquals( HTTP_STATUS_NOT_FOUND, $t_response->getStatusCode(), + "Creating a note on missing issue should fail" + ); + } + + public function testAddNoteWithTimeTracking() { + $this->skipIfTimeTrackingIsNotEnabled(); + + $this->createTestIssue(); + + $t_duration = '00:15'; + $t_payload = $this->generateNoteDataWithTimeTracking( 'Time tracking as HH:MM', $t_duration ); + $t_response = $this->addNote( $t_payload ); + $this->assertEquals( HTTP_STATUS_CREATED, $t_response->getStatusCode(), + "Creating a note with time tracking should succeed" + ); + $t_note = json_decode( $t_response->getBody() )->note; + $this->assertEquals( $t_note->time_tracking->duration, $t_duration, + "Created note duration should match payload" + ); + + $t_payload = $this->generateNoteDataWithTimeTracking( 'Time tracking as integer (minutes)', 90 ); + $t_response = $this->addNote( $t_payload ); + $this->assertEquals( HTTP_STATUS_CREATED, $t_response->getStatusCode(), + "Creating a note with time tracking should succeed" + ); + $t_note = json_decode( $t_response->getBody() )->note; + $this->assertEquals( '01:30', $t_note->time_tracking->duration, + "Duration in minutes should be converted to HH:MM" + ); + } + + public function testAddNoteWithAttachment() { + $this->createTestIssue(); + + $t_payload = $this->generateNoteData( 'Note with attachments' ); + $t_payload['files'] = [ + [ + 'name' => "test.txt", + 'content' => base64_encode( "Hello World" ) + ], + [ + 'name' => "logo.png", + 'content' => base64_encode( + file_get_contents( __DIR__ . "/../../images/mantis_logo.png" ) + ) + ], + ]; + $t_response = $this->addNote( $t_payload ); + $this->assertEquals( HTTP_STATUS_CREATED, $t_response->getStatusCode(), + "Creating a note with attachments should succeed" + ); + $t_note = json_decode( $t_response->getBody() )->note; + $this->assertCount( 2, $t_note->attachments, + "There should be 2 attachments" + ); + $this->assertStringStartsWith( 'text/plain', $t_note->attachments[0]->content_type, + "First attachment's MIME type should be plain text" + ); + $this->assertStringStartsWith( 'image/png', $t_note->attachments[1]->content_type, + "Second attachment's MIME type should be png" + ); + } + + public function testDeleteNote() { + $this->createTestIssue(); + + # Add test bugnote + $t_response = $this->addNote( $this->generateNoteData( 'Note to be deleted' ) ); + $this->assertEquals( HTTP_STATUS_CREATED, $t_response->getStatusCode() ); + $t_body = json_decode( $t_response->getBody() ); + $t_notes_count = count( $t_body->issue->notes ); + + # Delete it + $t_response = $this->builder()->delete( $this->endPoint( $t_body->note->id ) )->send(); + $this->assertEquals( HTTP_STATUS_SUCCESS, $t_response->getStatusCode() ); + $t_issue = json_decode( $t_response->getBody() ); + $this->assertCount( $t_notes_count - 1, + $t_issue->issue->notes ?? [], + "There should be one less note than before" + ); + } + + /** + * Create a test issue to add notes to. + * + * Sets $issueId property with the Id. + */ + private function createTestIssue(): void { + $t_issue_to_add = $this->getIssueToAdd(); + $t_response = $this->builder()->post( '/issues', $t_issue_to_add )->send(); + $this->assertEquals( HTTP_STATUS_CREATED, $t_response->getStatusCode() ); + $t_body = json_decode( $t_response->getBody(), true ); + $this->issueId = $t_body['issue']['id']; + $this->deleteIssueAfterRun( $this->issueId ); + } + + /** + * REST API endpoint for Bugnotes. + * + * @param int $p_note Optional Note ID. + * + * @return string + */ + private function endPoint( int $p_note = 0 ): string { + return '/issues/' . $this->issueId . '/notes/' . ( $p_note ?: '' ); + } + + /** + * Generates Bugnote data payload. + * + * @param string|null $p_text The text of the note to be included. + * If null, the text key is not set. + * + * @return array An associative array containing the Bugnote data. + */ + private function generateNoteData( ?string $p_text ): array { + $t_data = []; + if( $p_text !== null ) { + $t_data['text'] = $p_text; + } + return $t_data; + } + + /** + * Generates Bugnote data payload with time tracking. + * + * @param string $p_text The text of the note to be included. + * @param string|int $p_duration Time tracking duration. + * + * @return array An associative array containing the Bugnote data. + */ + private function generateNoteDataWithTimeTracking( string $p_text, $p_duration ): array { + $t_data = $this->generateNoteData( $p_text ); + $t_data['time_tracking']['duration'] = $p_duration; + return $t_data; + } + + /** + * Sends a REST API POST Bugnotes request. + * + * @param array $p_note_data Bugnote payload. + * + * @return ResponseInterface + */ + private function addNote( $p_note_data ): ResponseInterface { + return $this->builder()->post( $this->endPoint(), $p_note_data )->send(); + } +}
tests/rest/RestIssueTest.php+28 −0 modified@@ -115,6 +115,34 @@ public function testCreateIssueWithMinimalFields() { $this->deleteIssueAfterRun( $t_issue['id'] ); } + public function testCreateIssueWithLongText() { + $t_long_text = str_repeat( 'x', config_get_global( 'max_textarea_length' ) ); + + $t_fields = [ 'description', 'steps_to_reproduce', 'additional_information' ]; + foreach( $t_fields as $t_field ) { + $t_issue_to_add = $this->getIssueToAdd( $t_field ); + + # Maximum length + $t_issue_to_add[$t_field] = $t_long_text; + $t_response = $this->builder()->post( '/issues', $t_issue_to_add )->send(); + $this->assertEquals( HTTP_STATUS_CREATED, $t_response->getStatusCode(), + "Creating an issue with '$t_field' at maximum size should succeed" + ); + $t_body = json_decode( $t_response->getBody(), true ); + $t_issue = $t_body['issue']; + $this->deleteIssueAfterRun( $t_issue['id'] ); + + # Too long + $t_issue_to_add[$t_field] .= 'TOO LONG'; + $t_response = $this->builder()->post( '/issues', $t_issue_to_add )->send(); + $this->assertEquals( HTTP_STATUS_BAD_REQUEST, $t_response->getStatusCode(), + "Creating an issue with '$t_field' longer than max size should fail" + ); + } + + sleep(20); + } + public function testCreateIssueWithEnumIds() { $t_issue_to_add = $this->getIssueToAdd(); $t_issue_to_add['status']['id'] = 50; # assigned
tests/soap/IssueAddTest.php+30 −0 modified@@ -124,6 +124,36 @@ public function testCreateIssueWithHtmlMarkup() { } + /** + * Tests that creating an issue with long text fields bigger than the + * allowed maximum size fails. + * + * @return void + */ + public function testCreateIssueWithLongText() { + $t_long_text = str_repeat( 'x', config_get_global( 'max_textarea_length' ) ); + + $t_fields = [ 'description', 'steps_to_reproduce', 'additional_information' ]; + foreach( $t_fields as $t_field ) { + $t_issue_to_add = $this->getIssueToAdd(); + $t_issue_to_add[$t_field] = $t_long_text; + + # Maximum length + $t_issue_id = $this->client->mc_issue_add( $this->userName, $this->password, $t_issue_to_add ); + $this->deleteAfterRun( $t_issue_id ); + $t_issue = $this->client->mc_issue_get( $this->userName, $this->password, $t_issue_id ); + $this->assertEquals( $t_issue_to_add['description'], $t_issue->description ); + + # Too long + $t_issue_to_add[$t_field] .= ' TOO LONG'; + $this->expectException( SoapFault::class ); + $this->expectExceptionMessage( 'Long text fields must be shorter' ); + $this->client->mc_issue_add( $this->userName, $this->password, $t_issue_to_add );; + } + + $this->deleteAfterRun( $t_issue_id ); + } + /** * This issue tests the following: * 1. Creating an issue with some fields that are typically not used at creation time.
tests/soap/IssueNoteTest.php+23 −0 modified@@ -204,4 +204,27 @@ public function testAddNoteWithNoText() { } catch ( SoapFault $e ) { } } + + public function testAddNoteWithTextTooLong() { + $t_issue_to_add = $this->getIssueToAdd(); + $t_issue_id = $this->client->mc_issue_add( $this->userName, + $this->password, + $t_issue_to_add + ); + $this->deleteAfterRun( $t_issue_id ); + + $t_note_data = array( + 'text' => str_repeat( 'x', config_get_global( 'max_textarea_length' ) ), + 'note_type' => BUGNOTE + ); + + # Adding note with maximum length should be OK + $this->client->mc_issue_note_add( $this->userName, $this->password, $t_issue_id, $t_note_data ); + + $t_note_data['text'] .= ' TOO LONG NOW'; + $this->expectException( SoapFault::class ); + $this->expectExceptionMessage( 'Long text fields must be shorter' ); + $this->client->mc_issue_note_add( $this->userName, $this->password, $t_issue_id, $t_note_data ); + } + }
tests/soap/SoapBase.php+4 −4 modified@@ -24,11 +24,11 @@ * @link http://www.mantisbt.org */ +# Includes +require_once dirname( __DIR__ ) . '/TestConfig.php'; -$t_root_path = dirname( __DIR__, 2 ); - -# MantisBT constants -require_once ( $t_root_path . '/core/constant_inc.php' ); +# MantisBT Core API +require_mantis_core(); /** * Test cases for SoapEnum class.
c99a41272532Limit size of bug text fields
4 files changed · +40 −9
bug_report_page.php+16 −3 modified@@ -226,6 +226,7 @@ $t_show_due_date = in_array( 'due_date', $t_fields ) && access_has_project_level( config_get( 'due_date_update_threshold' ), helper_get_current_project(), auth_get_current_user_id() ); $t_show_attachments = in_array( 'attachments', $t_fields ) && file_allow_bug_upload(); $t_show_view_state = in_array( 'view_state', $t_fields ) && access_has_project_level( config_get( 'set_view_status_threshold' ) ); +$t_max_length = config_get_global( 'max_textarea_length' ); # don't index bug report page html_robots_noindex(); @@ -573,7 +574,11 @@ </th> <td> <?php # Newline after opening textarea tag is intentional, see #25839 ?> - <textarea class="form-control" <?php echo helper_get_tab_index() ?> id="description" name="description" cols="80" rows="10" required> + <textarea id="description" name="description" required + class="form-control" <?php echo helper_get_tab_index() ?> + cols="80" rows="10" + maxlength="<?php echo $t_max_length ?>" + > <?php echo string_textarea( $f_description ) ?> </textarea> </td> @@ -586,7 +591,11 @@ </th> <td> <?php # Newline after opening textarea tag is intentional, see #25839 ?> - <textarea class="form-control" <?php echo helper_get_tab_index() ?> id="steps_to_reproduce" name="steps_to_reproduce" cols="80" rows="10"> + <textarea id="steps_to_reproduce" name="steps_to_reproduce" + class="form-control" <?php echo helper_get_tab_index() ?> + cols="80" rows="10" + maxlength="<?php echo $t_max_length ?>" + > <?php echo string_textarea( $f_steps_to_reproduce ) ?> </textarea> </td> @@ -600,7 +609,11 @@ </th> <td> <?php # Newline after opening textarea tag is intentional, see #25839 ?> - <textarea class="form-control" <?php echo helper_get_tab_index() ?> id="additional_info" name="additional_info" cols="80" rows="10"> + <textarea id="additional_info" name="additional_info" + class="form-control" <?php echo helper_get_tab_index() ?> + cols="80" rows="10" + maxlength="<?php echo $t_max_length ?>" + > <?php echo string_textarea( $f_additional_info ) ?> </textarea> </td>
bug_update_page.php+13 −4 modified@@ -95,6 +95,7 @@ $t_bug_id = $f_bug_id; $t_action_button_position = config_get( 'action_button_position' ); +$t_max_textarea_length = config_get_global( 'max_textarea_length' ); $t_top_buttons_enabled = $t_action_button_position == POSITION_TOP || $t_action_button_position == POSITION_BOTH; $t_bottom_buttons_enabled = $t_action_button_position == POSITION_BOTTOM || $t_action_button_position == POSITION_BOTH; @@ -665,7 +666,9 @@ echo '</th>'; echo '<td colspan="5">'; echo '<textarea class="form-control" required ', helper_get_tab_index(), - ' cols="80" rows="10" id="description" name="description">', "\n", + ' cols="80" rows="10"', + ' maxlength="' . $t_max_textarea_length . '"', + ' id="description" name="description">', "\n", $t_description_textarea, '</textarea>'; echo '</td></tr>'; } @@ -676,7 +679,9 @@ echo '<th class="category"><label for="steps_to_reproduce">' . lang_get( 'steps_to_reproduce' ) . '</label></th>'; echo '<td colspan="5">'; echo '<textarea class="form-control" ', helper_get_tab_index(), - ' cols="80" rows="10" id="steps_to_reproduce" name="steps_to_reproduce">', "\n", + ' cols="80" rows="10"', + ' maxlength="' . $t_max_textarea_length . '"', + ' id="steps_to_reproduce" name="steps_to_reproduce">', "\n", $t_steps_to_reproduce_textarea, '</textarea>'; echo '</td></tr>'; } @@ -687,7 +692,9 @@ echo '<th class="category"><label for="additional_information">' . lang_get( 'additional_information' ) . '</label></th>'; echo '<td colspan="5">'; echo '<textarea class="form-control" ', helper_get_tab_index(), - ' cols="80" rows="10" id="additional_information" name="additional_information">', "\n", + ' cols="80" rows="10"', + ' maxlength="' . $t_max_textarea_length . '"', + ' id="additional_information" name="additional_information">', "\n", $t_additional_information_textarea, '</textarea>'; echo '</td></tr>'; } @@ -738,9 +745,11 @@ echo '<th class="category"><label for="bugnote_text">' . lang_get( 'add_bugnote_title' ) . '</label></th>'; echo '<td colspan="5"><textarea ', helper_get_tab_index(), ' id="bugnote_text" name="bugnote_text" class="', $t_bugnote_class, - '" cols="80" rows="7" maxlength="' . config_get_global( 'max_textarea_length' ) . '">', + '" cols="80" rows="7"', + ' maxlength="' . $t_max_textarea_length . '">', '</textarea></td></tr>'; + # Bugnote Private Checkbox (if permitted) if( access_has_bug_level( config_get( 'private_bugnote_threshold' ), $t_bug_id ) ) { echo '<tr>';
core/bug_api.php+4 −0 modified@@ -496,6 +496,10 @@ public function validate( $p_update_extended = true ) { error_parameters( lang_get( 'description' ) ); trigger_error( ERROR_EMPTY_FIELD, ERROR ); } + + helper_ensure_longtext_length_valid( $this->description, 'description' ); + helper_ensure_longtext_length_valid( $this->steps_to_reproduce, 'steps_to_reproduce' ); + helper_ensure_longtext_length_valid( $this->additional_information, 'additional_information' ); } # Make sure a category is set
core/commands/IssueAddCommand.php+7 −2 modified@@ -116,7 +116,6 @@ protected function validate() { ERROR_EMPTY_FIELD, array( 'summary' ) ); } - $t_summary = $t_issue['summary']; if( !isset( $t_issue['description'] ) || is_blank( $t_issue['description'] ) ) { @@ -125,8 +124,14 @@ protected function validate() { ERROR_EMPTY_FIELD, array( 'description' ) ); } - $t_description = $t_issue['description']; + helper_ensure_longtext_length_valid( $t_description, 'description' ); + + $t_steps_to_reproduce = $t_issue['steps_to_reproduce'] ?? ''; + helper_ensure_longtext_length_valid( $t_steps_to_reproduce, 'steps_to_reproduce' ); + + $t_additional_information = $t_issue['additional_information'] ?? ''; + helper_ensure_longtext_length_valid( $t_additional_information, 'additional_information' ); if( !isset( $t_issue['project'] ) ) { throw new ClientException(
d5cec6bffb44Limit size of bugnote text fields
7 files changed · +21 −8
bug_actiongroup_add_note_inc.php+3 −1 modified@@ -75,7 +75,9 @@ function action_add_note_print_fields() { <?php echo lang_get( 'add_bugnote_title' ); ?> </th> <td> - <textarea class="form-control" name="bugnote_text" id="bugnote_text" cols="80" rows="10"></textarea> + <textarea class="form-control" name="bugnote_text" id="bugnote_text" + cols="80" rows="10" maxlength="<?php echo DB_FIELD_SIZE_LONGTEXT ?>" + ></textarea> </td> </tr>
bug_actiongroup_page.php+3 −1 modified@@ -415,7 +415,9 @@ <?php echo lang_get( 'add_bugnote_title' ); ?> </th> <td> - <textarea name="bugnote_text" id="bugnote_text" class="<?php echo $t_bugnote_class ?>" cols="80" rows="7"></textarea> + <textarea name="bugnote_text" id="bugnote_text" class="<?php echo $t_bugnote_class ?>" + cols="80" rows="7" maxlength="<?php echo DB_FIELD_SIZE_LONGTEXT ?>" + ></textarea> </td> </tr> <?php
bugnote_add_inc.php+3 −1 modified@@ -116,7 +116,9 @@ <?php echo lang_get( 'bugnote' ) ?> </th> <td class="width-85"> - <textarea name="bugnote_text" id="bugnote_text" class="<?php echo $t_bugnote_class ?>" rows="7"></textarea> + <textarea name="bugnote_text" id="bugnote_text" class="<?php echo $t_bugnote_class ?>" + rows="7" maxlength="<?php echo DB_FIELD_SIZE_LONGTEXT ?>" + ></textarea> </td> </tr>
bugnote_edit_page.php+3 −2 modified@@ -130,8 +130,9 @@ <table class="table table-bordered table-condensed table-striped"> <tr> <td colspan="2"> - <textarea class="form-control <?php echo $t_bugnote_class; ?>" cols="80" rows="10" name="bugnote_text" - id="bugnote_text"><?php echo $t_bugnote_text ?></textarea> + <textarea id="bugnote_text" name="bugnote_text" class="form-control <?php echo $t_bugnote_class; ?>" + cols="80" rows="10" maxlength="<?php echo DB_FIELD_SIZE_LONGTEXT ?>" + ><?php echo $t_bugnote_text ?></textarea> </td> </tr> <?php if( config_get( 'time_tracking_enabled' ) ) { ?>
bug_reminder_page.php+3 −2 modified@@ -144,8 +144,9 @@ </select> </td> <td> - <textarea name="bugnote_text" cols="65" rows="10" - class="form-control <?php echo $t_bugnote_class; ?>"></textarea> + <textarea name="bugnote_text" class="form-control <?php echo $t_bugnote_class; ?>" + cols="65" rows="10" maxlength="<?php echo DB_FIELD_SIZE_LONGTEXT ?>" + ></textarea> </td> </tr> </table>
bug_update_page.php+4 −1 modified@@ -736,7 +736,10 @@ echo '<tr>'; echo '<th class="category"><label for="bugnote_text">' . lang_get( 'add_bugnote_title' ) . '</label></th>'; -echo '<td colspan="5"><textarea ', helper_get_tab_index(), ' id="bugnote_text" name="bugnote_text" class="', $t_bugnote_class, '" cols="80" rows="7"></textarea></td></tr>'; +echo '<td colspan="5"><textarea ', helper_get_tab_index(), + ' id="bugnote_text" name="bugnote_text" class="', $t_bugnote_class, + '" cols="80" rows="7" maxlength="' . DB_FIELD_SIZE_LONGTEXT . '">', + '</textarea></td></tr>'; # Bugnote Private Checkbox (if permitted) if( access_has_bug_level( config_get( 'private_bugnote_threshold' ), $t_bug_id ) ) {
core/bugnote_api.php+2 −0 modified@@ -267,6 +267,8 @@ function bugnote_add( $p_bug_id, $p_bugnote_text, $p_time_tracking = '0:00', $p_ # Event integration $t_bugnote_text = event_signal( 'EVENT_BUGNOTE_DATA', $p_bugnote_text, $c_bug_id ); + helper_ensure_longtext_length_valid( $t_bugnote_text, 'note' ); + # MySQL 4-bytes UTF-8 chars workaround #21101 $t_bugnote_text = db_mysql_fix_utf8( $t_bugnote_text );
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-r3jf-hm7q-qfw5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-46556ghsaADVISORY
- github.com/mantisbt/mantisbt/commit/c99a41272532ba49b5c8dccb7797afead9864234ghsax_refsource_MISCWEB
- github.com/mantisbt/mantisbt/commit/d5cec6bffb44d54bd412c186b9baa409b1aa4238ghsax_refsource_MISCWEB
- github.com/mantisbt/mantisbt/commit/e9119c68b4a0eaa0bbde3deb121e81f5f7157361ghsax_refsource_MISCWEB
- github.com/mantisbt/mantisbt/security/advisories/GHSA-r3jf-hm7q-qfw5ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.