diff --git a/benchmarks/independent/freeze-v3-php.json b/benchmarks/independent/freeze-v3-php.json new file mode 100644 index 0000000..61826ab --- /dev/null +++ b/benchmarks/independent/freeze-v3-php.json @@ -0,0 +1,561 @@ +{ + "schema_version": 1, + "population": "population-v3-php.toml", + "cases": [ + { + "id": "gitlist-commandline-cmdi", + "repo": "https://github.com/klaussilveira/gitlist", + "vulnerable": "f67609d52c1812fa8a7ed80eae5e795cfd72115f", + "fixed": "88cf2866083d5f7c20d9d565c45f828a7ad1516b", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sink_file": "src/SCM/System/Git/CommandLine.php", + "sink_functions": [ + "getDefaultBranch" + ], + "sink_sha256_vulnerable": "a0aa9125dabc9748274b3762b9bd1ad117400b1b8d446593afdc3e6f38951e26", + "sink_functions_missing": [], + "sink_sha256_fixed": "3f1c45d9f0ee68c517bf89fa3fe65c0ead84351ed5d9329ac5d6a4b53d575344", + "fix_changed_files": 2, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "ce5806c849f453d7200c1f488ee6aba19eef9a4c", + "tip": "ecc8e18e03d9b547e82b7c7c148cb00ba01c0cc9", + "subject": "Merge pull request #902 from zoredache/sort_repositories", + "files": [ + "src/App/Controller/Repository.php" + ], + "lines": 2, + "side": "before the vulnerable pin" + } + }, + { + "id": "qloapps-customermessage-bo-query-sqli", + "repo": "https://github.com/Qloapps/QloApps", + "vulnerable": "5b4d65cbb8570a4ba46364ee0f2ded79d294ab84", + "fixed": "123c97c110b7053ea3297d8e58fe95b3c3536560", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sites": [ + "classes/CustomerMessage.php::searchByName" + ], + "site_basis": { + "classes/CustomerMessage.php::searchByName": "resolved-from-fix-diff" + }, + "site_files_unresolved": {}, + "sink_file": "classes/CustomerMessage.php", + "sink_functions": [ + "searchByName" + ], + "sink_sha256_vulnerable": "f4037f17c83d248639dbb6cce36f35e3b7eaae557dd631bc9268a97415c46534", + "sink_functions_missing": [], + "sink_sha256_fixed": "0464ba7047c393a2c873f301717258397bfc50c2b4f423e98f33d0c954678d41", + "fix_changed_files": 5, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "884563ccff83fe42b4689ba32e605276278c3989", + "tip": "e43b45009508eb1465709d4af0f549faacd1d817", + "subject": "Merge pull request #1735 from amitsharma322/gli-3007", + "files": [ + "classes/PaymentModule.php", + "controllers/admin/AdminOrdersController.php" + ], + "lines": 23, + "side": "after the fix" + } + }, + { + "id": "roundcube-markasjunk-cmd-learn-rce", + "repo": "https://github.com/roundcube/roundcubemail", + "vulnerable": "a1afb8fd1f00ed4cb9376c072bb5ca5ded64495e", + "fixed": "b8f90e28a46d42e79a69568cba897f8f4223d9cd", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sites": [ + "plugins/markasjunk/drivers/cmd_learn.php::_do_salearn" + ], + "site_basis": { + "plugins/markasjunk/drivers/cmd_learn.php::_do_salearn": "resolved-from-fix-diff" + }, + "site_files_unresolved": {}, + "sink_file": "plugins/markasjunk/drivers/cmd_learn.php", + "sink_functions": [ + "_do_salearn" + ], + "sink_sha256_vulnerable": "f4f46e54fa8a19d20ab4000e0f363afd544c6f9f73b806c7b8176fd8348dea83", + "sink_functions_missing": [], + "sink_sha256_fixed": "3082db4ed8a79b472ebc2b7d42e34db054737297a81cf595b652a6ba4e34f8eb", + "fix_changed_files": 2, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "1cddafded973c4bf6faa173accaf8f52ca1ac0d0", + "tip": "747f544708ed1d698b677d333346164a9451aeda", + "subject": "Simplify", + "files": [ + "tests/Framework/TnefDecoderTest.php" + ], + "lines": 6, + "side": "after the fix" + } + }, + { + "id": "maccms10-template-blacklist-rce", + "repo": "https://github.com/magicblack/maccms10", + "vulnerable": "2cabecda29947e8f9ea7cd3aa47ab90ed817605b", + "fixed": "71ad3bb29570e110d8e973acff68040a3050ddf0", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sites": [ + "application/admin/controller/Template.php::info" + ], + "site_basis": { + "application/admin/controller/Template.php::info": "resolved-from-fix-diff" + }, + "site_files_unresolved": {}, + "sink_file": "application/admin/controller/Template.php", + "sink_functions": [ + "info" + ], + "sink_sha256_vulnerable": "02e0ff0d172a25fc35b5b386c4280cf65bf3980a612cd833020e43f2dddb5d3c", + "sink_functions_missing": [], + "sink_sha256_fixed": "310d667aa2a99f554f49649b0ff1549b0c7f35bf1a0c167a86569c49167f5cc9", + "fix_changed_files": 1, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "d9f09011a1523d0bfaed5dea9b2139461f2b0ea8", + "tip": "668ba418e50e96ab32ac612f3b6528beb172b3b7", + "subject": "fix #1356 \u4fee\u6b63\u6ce8\u518cAPI\u6821\u9a8c\u5668\u89e3\u6790\u9519\u8bef\u53ca\u7f3a\u53c2\u62a5\u9519", + "files": [ + "application/api/controller/User.php", + "application/common/model/User.php", + "tests/regression/user_register_validate.php", + "\"\\350\\257\\264\\346\\230\\216\\346\\226\\207\\346\\241\\243/API\\346\\216\\245\\345\\217\\243\\350\\257\\264\\346\\230\\216V2.txt\"" + ], + "lines": 166, + "side": "after the fix" + } + }, + { + "id": "mantisbt-tokenizer-eval-rce", + "repo": "https://github.com/mantisbt/mantisbt", + "vulnerable": "e7041ecc80c8d35f76a7ea1027a8a231244f7dc5", + "fixed": "78c0af63d1fe0118004744cab21ca3bf2cea0f5c", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sites": [ + "core/classes/Tokenizer.class.php::__construct" + ], + "site_basis": { + "core/classes/Tokenizer.class.php::__construct": "resolved-from-fix-diff" + }, + "site_files_unresolved": { + "adm_config_set.php": "the fix does not change this file" + }, + "sink_file": "core/classes/Tokenizer.class.php", + "sink_functions": [ + "__construct" + ], + "sink_sha256_vulnerable": "cda7f81f7fcb4675c7bf5fc3f8dfd41ccb48ad891e95a85cf5edae90f0cbb1df", + "sink_evidence_found": true, + "sink_sha256_fixed": "f555c63e2dc15dbef7ff2c4db8d7c47848c8bc7abe981364a11f65c5e44b8fb0", + "fix_changed_files": 1, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "088fa767bce246b6d2f083f32d937f22c8de4767", + "tip": "d6ca719b591f70acfa711fc41705388557b6ac04", + "subject": "Improve Workflow Graph display.", + "files": [ + "workflow_graph_img.php" + ], + "lines": 14, + "side": "after the fix" + } + }, + { + "id": "sharp-editor-iframe-srcdoc-xss", + "repo": "https://github.com/code16/sharp", + "vulnerable": "67743149309054493ec8f5505173fc57aaff760f", + "fixed": "ec509a22c808a5bd9dfad6a0a85c92ce6f411e21", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sites": [ + "src/Utils/Sanitization/FormatsSanitizedValue.php::sanitizer", + "src/Utils/Sanitization/FormatsSanitizedValue.php::isEncodingNeeded" + ], + "site_basis": { + "src/Utils/Sanitization/FormatsSanitizedValue.php::sanitizer": "resolved-from-fix-diff", + "src/Utils/Sanitization/FormatsSanitizedValue.php::isEncodingNeeded": "resolved-from-fix-diff" + }, + "site_files_unresolved": {}, + "sink_file": "src/Utils/Sanitization/FormatsSanitizedValue.php", + "sink_functions": [ + "sanitizer", + "isEncodingNeeded" + ], + "sink_sha256_vulnerable": "7e65b0fbcd481c6322f83ae46523b069c4a62383cbf088e7be60be6ace4bcc9a", + "sink_evidence_found": true, + "sink_sha256_fixed": "59a2845f4a8b419f8504b2cb539087eb3dc3fb8bb0ec7c950330ba951c6c75ad", + "fix_changed_files": 2, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "ec509a22c808a5bd9dfad6a0a85c92ce6f411e21", + "tip": "7c601fffe930fc321a5d8bcb25cb072d50a6fc08", + "subject": "Update version to v9.22.5", + "files": [ + "src/SharpInternalServiceProvider.php" + ], + "lines": 2, + "side": "after the fix" + } + }, + { + "id": "privatebin-jsonld-request-uri-injection", + "repo": "https://github.com/PrivateBin/PrivateBin", + "vulnerable": "e0dd4c025c19a182b6a4c6fb77a8bf81ceff6899", + "fixed": "75f056dcda955d94c17ec5a4f8c54a9b7bfcee07", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sink_file": "lib/Request.php", + "sink_functions": [ + "getRequestUri" + ], + "sink_sha256_vulnerable": "bd35439be97ac659d94b1d0cde13f70644f99468bfc16ddc2fb214a845bdb7b4", + "sink_functions_missing": [], + "sink_sha256_fixed": "ea17a346372b8259bad1ac7edb1268378168d0de0628cd53b948f52038fd9c1e", + "fix_changed_files": 4, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "75f056dcda955d94c17ec5a4f8c54a9b7bfcee07", + "tip": "3035d6e5d3cceb7f06f7b7bb80968727879c52b0", + "subject": "incrementing version", + "files": [ + "CHANGELOG.md", + "Makefile", + "README.md", + "SECURITY.md", + "doc/Installation.md", + "js/package.json", + "lib/Controller.php" + ], + "lines": 18, + "side": "after the fix" + } + }, + { + "id": "openstamanager-group-name-html-injection", + "repo": "https://github.com/devcode-it/openstamanager", + "vulnerable": "57a2734e2121aa8fcd456eff7c59f03e29e69176", + "fixed": "0b88ad1963871abfa5e134004ca0c74c496d8d2f", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sink_file": "modules/utenti/edit.php", + "sink_functions": [], + "sink_sha256_vulnerable": "8524dd092b2fdbf68339b80e374c4108fd6925a146223b325370f4ee8414a016", + "sink_evidence_found": true, + "sink_sha256_fixed": "290d5fde2df7713eb592361f3b0abfd3524a3b83bf99ef0fafdf80f0ee29612b", + "fix_changed_files": 4, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "0b88ad1963871abfa5e134004ca0c74c496d8d2f", + "tip": "1832b85d0e570e0b873c7c23dcfcb6b5d1365a28", + "subject": "fix: ricerca riferimenti in importazione fattura per riga con articolo non esistente", + "files": [ + "plugins/importFE/actions.php" + ], + "lines": 2, + "side": "after the fix" + } + }, + { + "id": "wp-geo-my-wp-info-window-lfi", + "repo": "https://github.com/Fitoussi/geo-my-wp", + "vulnerable": "1dbb7b1672a45a8f88fdcd379ceb07b6cf2beceb", + "fixed": "5a768bf1c6e44ded83a65be8789f3587515665ac", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sites": [ + "plugins/posts-locator/includes/gmw-posts-locator-functions.php::gmw_posts_locator_ajax_info_window_loader", + "includes/gmw-functions.php::gmw_ajax_info_window_init" + ], + "site_basis": { + "plugins/posts-locator/includes/gmw-posts-locator-functions.php::gmw_posts_locator_ajax_info_window_loader": "advisory-named", + "includes/gmw-functions.php::gmw_ajax_info_window_init": "resolved-from-fix-diff" + }, + "site_files_unresolved": {}, + "sink_file": "plugins/posts-locator/includes/gmw-posts-locator-functions.php", + "sink_functions": [ + "gmw_posts_locator_ajax_info_window_loader" + ], + "sink_sha256_vulnerable": "605e777dab7653792d5ae2f2ef6c776f76d1fb1a271d0446c65f6ebf1dfb8461", + "sink_functions_missing": [], + "sink_sha256_fixed": "32166bf3941e0a5048b58c49884d513a1be9b11722245f3b7d4d072846400ec2", + "fix_changed_files": 5, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "9c394482d1509ca9222365a91117a10b19619be8", + "tip": "85bcd85541c777b6fd8580cb1915504a2a2b84cf", + "subject": "Release 4.5.5.2", + "files": [ + "geo-my-wp.php", + "readme.txt" + ], + "lines": 17, + "side": "before the vulnerable pin" + } + }, + { + "id": "kirby-realpath-sibling-traversal", + "repo": "https://github.com/getkirby/kirby", + "vulnerable": "e90257fd7504053e13e93a1e9a5a9a5137aed966", + "fixed": "2b6fab950bc89d505ea89576bf71c8de614cc3b2", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sink_file": "src/Filesystem/Dir.php", + "sink_functions": [ + "realpath" + ], + "sink_sha256_vulnerable": "d17a09853356ed1d2e08421e3130a4fd0e68dc6c40f3734c796d425ea135f8d3", + "sink_functions_missing": [], + "sink_sha256_fixed": "e0ec9c78eff595b6076ca396646a097a7f37a4f49212c2bc7ef4aa0d68448652", + "fix_changed_files": 4, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "bbc7c251efcbb01a72f5786af15964e596dc6c4d", + "tip": "65dfca053d78bac574c277404adbc8175b2844d8", + "subject": "fix: Improve content salt message", + "files": [ + "i18n/translations/en.json", + "tests/Panel/Areas/SystemTest.php" + ], + "lines": 4, + "side": "before the vulnerable pin" + } + }, + { + "id": "mineadmin-appstore-identifier-traversal", + "repo": "https://github.com/mineadmin/MineAdmin", + "vulnerable": "ac0dcad616c5d304c249d1db4a6675f65cf736dc", + "fixed": "ca41902a2a5422676227e5088f4cc1dec06044f1", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sink_file": "plugin/mine-admin/app-store/src/Service/Service.php", + "sink_functions": [ + "download", + "install", + "unInstall" + ], + "sink_sha256_vulnerable": "d06a053c26f7918a179ead518f70d79d60c2211bcbe751185b95cead8a8af22e", + "sink_functions_missing": [], + "sink_sha256_fixed": "b4f2cd97704d2eb76eae07287fd2251d5e072de1a5cba333857a7cc1fd23747c", + "fix_changed_files": 2, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "1f99cfd595f9621f4be572d970821f3509acfe33", + "tip": "15deedde8c106842d9c3584d8680e2f65adaaac1", + "subject": "Upgrade the MineAdmin to 3.2-alpha.1, and update Hyperf to the 3.2 release.", + "files": [ + "composer.json", + "config/autoload/cache.php" + ], + "lines": 105, + "side": "after the fix" + } + }, + { + "id": "pagible-admin-proxy-dns-rebinding-ssrf", + "repo": "https://github.com/aimeos/pagible", + "vulnerable": "03f03b2f81b20b1c1625ff227235c43791ddbb33", + "fixed": "09a8205d513ec89ed22cdd7bdae0f4c181cee082", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "inferred_fix_changes_sites": { + "admin/src/Controllers/AdminController.php::proxy": true, + "core/src/Utils.php::isValidUrl": true + }, + "sink_file": "admin/src/Controllers/AdminController.php", + "sink_functions": [ + "proxy" + ], + "sink_sha256_vulnerable": "cedb2973084f6ba825df0f4cf0d9edc1270c25e236155da0a1f7dddb8fa21340", + "sink_functions_missing": [], + "sink_sha256_fixed": "eb6a56affa39cb0aa88e894227c2dc871f6a7dc508a8121d205cb8bb3c2105a9", + "fix_changed_files": 3, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "981b8d63e52e7ba8875a9a52c1865eb3bc662154", + "tip": "d7577efd68e9c5e36436c8572bfed0df2be2c634", + "subject": "Publish related items (#210)", + "files": [ + "core/src/Commands/Publish.php", + "core/src/Models/Element.php", + "core/src/Models/Page.php", + "graphql/src/GraphQL/Mutations/PubElement.php", + "graphql/src/GraphQL/Mutations/PubPage.php", + "graphql/tests/GraphqlPageTest.php", + "mcp/src/Tools/PublishElement.php", + "mcp/src/Tools/PublishPage.php" + ], + "lines": 44, + "side": "after the fix" + } + }, + { + "id": "wp-graphql-updatepost-publish-bypass", + "repo": "https://github.com/wp-graphql/wp-graphql", + "vulnerable": "d53b0e566f20a8df0664559cb6432d52a7cd285c", + "fixed": "55441663eaa33c3f2e05de038c8286c845916461", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sites": [ + "plugins/wp-graphql/src/Mutation/PostObjectUpdate.php::mutate_and_get_payload" + ], + "site_basis": { + "plugins/wp-graphql/src/Mutation/PostObjectUpdate.php::mutate_and_get_payload": "resolved-from-fix-diff" + }, + "site_files_unresolved": {}, + "sink_file": "plugins/wp-graphql/src/Mutation/PostObjectUpdate.php", + "sink_functions": [ + "mutate_and_get_payload" + ], + "sink_sha256_vulnerable": "e39fe898c11102ea902c0db1a97cfc641c676d88d54bbb12d50c67ac9f9e4447", + "sink_functions_missing": [], + "sink_sha256_fixed": "744928c8eeefdd40b1c7f48ce6ae7fb71f07ff26a95b26321725eaf4bffebdde", + "fix_changed_files": 4, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "b583bb6ec87016965085afc7ea586f114c711c68", + "tip": "cc12aed380414545a005048bfcebe4ba50e9c8f9", + "subject": "fix: point acf.wpgraphql.com links at the wpgraphql.com docs portal (#4251)", + "files": [ + "plugins/wp-graphql-acf/README.md", + "plugins/wp-graphql-acf/docs/field-types/advanced-link.md", + "plugins/wp-graphql-acf/docs/field-types/button-group.md", + "plugins/wp-graphql-acf/docs/field-types/checkbox.md", + "plugins/wp-graphql-acf/docs/field-types/code-editor.md", + "plugins/wp-graphql-acf/docs/field-types/color-picker.md", + "plugins/wp-graphql-acf/docs/field-types/countries.md", + "plugins/wp-graphql-acf/docs/field-types/currencies.md", + "plugins/wp-graphql-acf/docs/field-types/date-picker.md", + "plugins/wp-graphql-acf/docs/field-types/date-range-picker.md", + "plugins/wp-graphql-acf/docs/field-types/datetimepicker.md", + "plugins/wp-graphql-acf/docs/field-types/email.md", + "plugins/wp-graphql-acf/docs/field-types/file.md", + "plugins/wp-graphql-acf/docs/field-types/gallery.md", + "plugins/wp-graphql-acf/docs/field-types/google-map.md", + "plugins/wp-graphql-acf/docs/field-types/group.md", + "plugins/wp-graphql-acf/docs/field-types/image-selector.md", + "plugins/wp-graphql-acf/docs/field-types/image-sizes.md", + "plugins/wp-graphql-acf/docs/field-types/image.md", + "plugins/wp-graphql-acf/docs/field-types/languages.md", + "plugins/wp-graphql-acf/docs/field-types/link.md", + "plugins/wp-graphql-acf/docs/field-types/radio-button.md", + "plugins/wp-graphql-acf/docs/field-types/range.md", + "plugins/wp-graphql-acf/docs/field-types/select.md", + "plugins/wp-graphql-acf/docs/field-types/taxonomies.md", + "plugins/wp-graphql-acf/docs/field-types/taxonomy-terms.md", + "plugins/wp-graphql-acf/docs/field-types/taxonomy.md", + "plugins/wp-graphql-acf/docs/field-types/text-area.md", + "plugins/wp-graphql-acf/docs/field-types/text.md", + "plugins/wp-graphql-acf/docs/field-types/time-picker.md", + "plugins/wp-graphql-acf/docs/field-types/true-false.md", + "plugins/wp-graphql-acf/docs/field-types/url.md", + "plugins/wp-graphql-acf/docs/field-types/user-roles.md", + "plugins/wp-graphql-acf/docs/field-types/user.md", + "plugins/wp-graphql-acf/docs/field-types/wysiwyg.md", + "plugins/wp-graphql-acf/readme.txt", + "plugins/wp-graphql/docs/compatibility-guide.md", + "plugins/wp-graphql/src/Admin/AdminNotices.php", + "plugins/wp-graphql/src/Admin/Extensions/Registry.php" + ], + "lines": 90, + "side": "before the vulnerable pin" + } + }, + { + "id": "sylius-mollie-thankyou-order-idor", + "repo": "https://github.com/Sylius/MolliePlugin", + "vulnerable": "7400f1489a4b699df97ced9499acde4faaa476d5", + "fixed": "01316b3ad3cf82e3c5ad160115d0a2cf89174e49", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sink_file": "src/Controller/Shop/PageRedirectController.php", + "sink_functions": [ + "thankYouAction" + ], + "sink_sha256_vulnerable": "60578839af598e79b4f3792ad31e8a2c80a360c53fd75609bc25acfb68c4fcd9", + "sink_functions_missing": [], + "sink_sha256_fixed": "950d09980903953d802456cafed8a3efb4f9f0e953cdf49482e9ab4a9ac68de6", + "fix_changed_files": 7, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "7cc02612b4c6b4403a2b0a1bcef1eb7c5382f505", + "tip": "45b01569853d16d04ffd50df281e2fd607650864", + "subject": "[UPMERGE] 3.2 -> 3.3 (#333)", + "files": [ + "src/Client/MollieApiClient.php", + "src/SyliusMolliePlugin.php", + "src/Twig/Extension/MolliePluginLatestVersion.php" + ], + "lines": 21, + "side": "before the vulnerable pin" + } + }, + { + "id": "wp-podlove-generation-guard-poi", + "repo": "https://github.com/podlove/podlove-publisher", + "vulnerable": "4eedbd66796942ab052de2dbd6311a24cc75c4a7", + "fixed": "ea878175f2612d54888d9e2aa49ae4db93b724fd", + "problems": [], + "vulnerable_is_ancestor_of_fixed": true, + "sites": [ + "lib/modules/shownotes/rest_api.php::create_link_item", + "lib/image_cache/generation_guard.php::__destruct", + "lib/model/base.php::to_array" + ], + "site_basis": { + "lib/modules/shownotes/rest_api.php::create_link_item": "advisory-named", + "lib/image_cache/generation_guard.php::__destruct": "advisory-named", + "lib/model/base.php::to_array": "resolved-from-fix-diff" + }, + "site_files_unresolved": {}, + "inferred_fix_changes_sites": { + "lib/modules/shownotes/rest_api.php::create_link_item": true, + "lib/image_cache/generation_guard.php::__destruct": false + }, + "sink_file": "lib/modules/shownotes/rest_api.php", + "sink_functions": [ + "create_link_item" + ], + "sink_sha256_vulnerable": "5023f7950ad450c6f0e9ca9bec474f161a509b0b3ddbf8f9f495593738dd8a05", + "sink_functions_missing": [], + "sink_sha256_fixed": "316f85861d95e63a9a7cc1f089e2b6c3f1ac61aa92092e8b279534b226fe042f", + "fix_changed_files": 9, + "fix_touches_sink_file": true, + "fix_touches_fix_site": false, + "benign_proposal": { + "base": "af187ad4bba2739839a0aa1e6cf2cabd6f9b99f5", + "tip": "50da8db0f5a85afaf27cd6199940be0403b368ec", + "subject": "fix PHP warning", + "files": [ + "lib/modules/contributors/contributor_list_table.php" + ], + "lines": 2, + "side": "after the fix" + } + } + ] +} diff --git a/benchmarks/independent/freeze.py b/benchmarks/independent/freeze.py index 303bdd3..038bbd0 100644 --- a/benchmarks/independent/freeze.py +++ b/benchmarks/independent/freeze.py @@ -78,22 +78,179 @@ def sink_parts(sink: str) -> tuple[str, list[str]]: return path.strip(), functions -def benign(repo: Path, fixed: str, sink_file: str, extensions: tuple[str, ...]) -> dict[str, object] | None: +PHP_FUNCTION = re.compile(r"\bfunction\s*&?\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(") + + +def php_code(text: str) -> str: + """`text` with comments, string contents and inline HTML blanked to spaces, newlines kept (line numbers hold). + + Enough to find function bodies by brace matching; heredoc/nowdoc bodies are blanked too. Not a parser: a file + that defeats it yields no function, and the site falls back to ``, which the freeze record shows.""" + out, i, n, php = list(text), 0, len(text), False + + def blank(start: int, end: int) -> None: + for k in range(start, min(end, n)): + if out[k] != "\n": + out[k] = " " + + while i < n: + if not php: + j = text.find("", i): + php, i = False, i + 2 + elif text.startswith("//", i) or text[i] == "#" and not text.startswith("#[", i): + j = min(k for k in (text.find("\n", i), text.find("?>", i), n) if k >= 0) + blank(i, j) + i = j + elif text.startswith("/*", i): + j = text.find("*/", i + 2) + j = n if j < 0 else j + 2 + blank(i, j) + i = j + elif text.startswith("<<<", i): + label = re.match(r"<<<\s*['\"]?([A-Za-z_][A-Za-z0-9_]*)", text[i:]) + end = re.compile(r"^\s*" + re.escape(label.group(1)) + r"\b", re.M) if label else None + found = end.search(text, text.find("\n", i) + 1) if end else None + j = found.end() if found else i + 3 + blank(i, j) + i = j + elif text[i] in "'\"": + quote, j = text[i], i + 1 + while j < n and text[j] != quote: + j += 2 if text[j] == "\\" else 1 + blank(i + 1, j) + i = j + 1 + else: + i += 1 + return "".join(out) + + +def php_functions(text: str) -> list[tuple[str, int, int]]: + """(name, first line, last line) of every named function or method body in a PHP file, 1-based.""" + code, spans = php_code(text), [] + for match in PHP_FUNCTION.finditer(code): + brace, semi = code.find("{", match.end()), code.find(";", match.end()) + if brace < 0 or 0 <= semi < brace: # abstract or interface method: no body + continue + depth, k = 0, brace + while k < len(code): + depth += {"{": 1, "}": -1}.get(code[k], 0) + if depth == 0: + break + k += 1 + spans.append((match.group(1), code.count("\n", 0, match.start()) + 1, code.count("\n", 0, k) + 1)) + return spans + + +def enclosing(spans: list[tuple[str, int, int]], line: int, inserted: bool = False) -> str: + """The innermost named function containing `line`, or ``. For a pure insertion, `line` is the line the + new code follows, so a function whose closing brace is on that line does not contain it.""" + inside = [(last - first, name) for name, first, last in spans if first <= line <= last and not (inserted and line == last)] + return min(inside)[1] if inside else "" + + +def old_hunks(repo: Path, vulnerable: str, fixed: str, path: str) -> list[tuple[int, int, bool]]: + """Vulnerable-side line ranges the fix changes in `path`, and whether each is a pure insertion (then the range + is the line it follows, at least 1).""" + diff = git(repo, "diff", "-U0", vulnerable, fixed, "--", path) + ranges = [] + for start, count in re.findall(r"^@@ -(\d+)(?:,(\d+))? \+", diff, re.M): + first, size = int(start), 1 if count == "" else int(count) + ranges.append((max(first, 1), max(first, 1), True) if size == 0 else (first, first + size - 1, False)) + return ranges + + +def changed_functions(repo: Path, vulnerable: str, fixed: str, path: str) -> list[str]: + """The functions (or ``) of `path` at the vulnerable pin that the fix's hunks touch, in hunk order.""" + text = (blob(repo, vulnerable, path) or b"").decode(errors="replace") + spans, names = php_functions(text), [] + for first, last, inserted in old_hunks(repo, vulnerable, fixed, path): + for line in range(first, last + 1): + name = enclosing(spans, line, inserted) + # New code added between functions (a new helper) changes no vulnerable-side code: no site. + if name not in names and not (inserted and name == ""): + names.append(name) + return names + + +def resolve_sites(repo: Path, case: dict[str, object], changed: list[str]) -> dict[str, object]: + """Decision 2 of population v3: a file the advisory names without a function gets, as its declared site, the + function(s) the fix's diff of that file changes. Advisory-named sites are kept and marked as such.""" + declared = [str(s) for s in case.get("sites", [])] # type: ignore[union-attr] + recorded = case.get("site_basis") or {} # a frozen population keeps each site's basis + basis = {site: recorded.get(site, "advisory-named") for site in declared} # type: ignore[union-attr] + unresolved = {} + named_files = {site.partition("::")[0] for site in declared} + for path in [str(f) for f in case.get("site_files", [])]: # type: ignore[union-attr] + if path in named_files: + continue + if path not in changed: + unresolved[path] = "the fix does not change this file" + continue + for name in changed_functions(repo, str(case["vulnerable"]), str(case["fixed"]), path): + basis.setdefault(f"{path}::{name}", "resolved-from-fix-diff") + return {"sites": list(basis), "site_basis": basis, "site_files_unresolved": unresolved} + + +def fix_changes_sites(repo: Path, case: dict[str, object], sites: list[str]) -> dict[str, bool]: + """Per declared site: does the fix's diff change a line inside that function (or the file, for ``)?""" + result = {} + for site in sites: + path, _, name = site.partition("::") + hunks = old_hunks(repo, str(case["vulnerable"]), str(case["fixed"]), path) + if name == "" or not hunks: + result[site] = bool(hunks) + continue + spans = php_functions((blob(repo, str(case["vulnerable"]), path) or b"").decode(errors="replace")) + result[site] = any(enclosing(spans, line, inserted) == name for first, last, inserted in hunks for line in range(first, last + 1)) + return result + + +def benign( + repo: Path, fixed: str, sink_file: str, extensions: tuple[str, ...], strict: bool = False, vulnerable: str = "" +) -> dict[str, object] | None: + """The first acceptable change after the fix (see the module docstring). + + `strict` (a single-language population, v3) tightens the proposal: the change is measured against its first + parent, so a merge counts with everything it brings in, and every message it brings in is checked for security + words (a branch-sync merge can carry the fix itself); it must change a file of the population's language; and + when no commit after the fix qualifies, the history before the vulnerable pin is searched, newest first.""" head = git(repo, "rev-parse", "origin/HEAD", check=False).strip() or git(repo, "rev-parse", "HEAD").strip() - commits = git(repo, "rev-list", "--first-parent", "--reverse", f"{fixed}..{head}", check=False).split()[:400] + span = [f"{fixed}..{head}"] + if subprocess.run(["git", "-C", str(repo), "merge-base", "--is-ancestor", fixed, head], check=False).returncode: + # A fix on a maintenance branch (population v3: kirby 4.x, roundcube 1.6): the default branch's commits + # after the fix date, not its whole history outside the branch. + span = [f"--since={git(repo, 'log', '-1', '--format=%cI', fixed).strip()}", head] + commits = git(repo, "rev-list", "--first-parent", "--reverse", *span, check=False).split()[:400] + after = set(commits) + if strict and vulnerable: + commits += git(repo, "rev-list", "--first-parent", "--max-count=400", f"{vulnerable}^", check=False).split() for commit in commits: - message = git(repo, "log", "-1", "--format=%s%n%b", commit) + parent = git(repo, "rev-parse", f"{commit}^", check=False).strip() + if not parent: + continue + if strict: + message = git(repo, "log", "--format=%s%n%b", f"{parent}..{commit}") + stat = git(repo, "diff", "--numstat", parent, commit).splitlines() + else: + message = git(repo, "log", "-1", "--format=%s%n%b", commit) + stat = git(repo, "show", "--numstat", "--format=", commit).splitlines() if SECURITY_WORDS.search(message): continue - stat = git(repo, "show", "--numstat", "--format=", commit).splitlines() files = [line.split("\t") for line in stat if line.count("\t") == 2] if not files or any(f[2] == sink_file for f in files): continue source = [f for f in files if f[2].endswith(extensions)] lines = sum(int(a) + int(b) for a, b, _ in files if a.isdigit() and b.isdigit()) if source and 0 < lines <= MAX_BENIGN_LINES: - parent = git(repo, "rev-parse", f"{commit}^").strip() - return {"base": parent, "tip": commit, "subject": message.splitlines()[0][:120], "files": [f[2] for f in files], "lines": lines} + subject = git(repo, "log", "-1", "--format=%s", commit).strip() + side = "after the fix" if commit in after else "before the vulnerable pin" + return {"base": parent, "tip": commit, "subject": subject[:120], "files": [f[2] for f in files], "lines": lines, "side": side} return None @@ -112,7 +269,22 @@ def verify(case: dict[str, object], cache: Path) -> dict[str, object]: record["vulnerable_is_ancestor_of_fixed"] = ordered if not ordered: problems.append("the vulnerable pin is not an ancestor of the fixed pin") + changed = git(repo, "diff", "--name-only", vulnerable, fixed).splitlines() sites = [str(site) for site in case.get("sites", [])] # type: ignore[union-attr] + if case.get("site_files"): + # Population v3: files the advisory names without a function are resolved from the fix's diff. + record.update(resolve_sites(repo, case, changed)) + sites = record["sites"] # type: ignore[assignment] + if not sites: + problems.append("no declared site: the advisory names no function and the fix changes no named file") + if case.get("fix_named_by_advisory") is False: + # An inferred fix must change the declared sink file and at least one declared site function. + basis = record.get("site_basis") or {} + named = [s for s in sites if basis.get(s, "advisory-named") == "advisory-named"] # type: ignore[union-attr] + touched = fix_changes_sites(repo, case, named) + record["inferred_fix_changes_sites"] = touched + if not any(touched.values()): + problems.append("the inferred fix changes none of the declared site functions: drop the case") if sites: # Declared `path::function` sites (population v2) are what the verification reads; the first names the # sink file, and every function declared in that file must appear in it. @@ -139,7 +311,6 @@ def verify(case: dict[str, object], cache: Path) -> dict[str, object]: problems.append(f"none of {functions} appears in {sink_file} at the vulnerable pin") if after is not None: record["sink_sha256_fixed"] = hashlib.sha256(after).hexdigest() - changed = git(repo, "diff", "--name-only", vulnerable, fixed).splitlines() record["fix_changed_files"] = len(changed) record["fix_touches_sink_file"] = sink_file in changed fix_site = str(case.get("fix_site", "")).partition("::")[0].strip() @@ -148,7 +319,10 @@ def verify(case: dict[str, object], cache: Path) -> dict[str, object]: problems.append(f"the fix changes neither {sink_file} nor a declared fix_site") languages = [str(case["language"]), *[str(x) for x in case.get("also", [])]] # type: ignore[union-attr] extensions = tuple(e for lang in languages for e in EXTENSIONS.get(lang, ())) - record["benign_proposal"] = benign(repo, fixed, sink_file, extensions) + strict = bool(case.get("_population_language")) + if strict: + extensions = EXTENSIONS.get(str(case["_population_language"]), extensions) + record["benign_proposal"] = benign(repo, fixed, sink_file, extensions, strict, vulnerable) if record["benign_proposal"] is None and not case.get("benign"): problems.append("no benign change found automatically; choose one by hand") return record @@ -161,7 +335,11 @@ def main() -> int: parser.add_argument("--population", type=Path, default=POPULATION, help="a population-vN.toml in this directory") args = parser.parse_args() population = args.population if args.population.is_absolute() else HERE / args.population.name - cases = tomllib.loads(population.read_text())["case"] + data = tomllib.loads(population.read_text()) + cases = data["case"] + for case in cases: + # A single-language population (v3) gets the strict benign proposal (see `benign`). + case["_population_language"] = data.get("language", "") only = {c.strip() for c in args.only.split(",") if c.strip()} records = [] for case in cases: diff --git a/benchmarks/independent/population-v3-php.toml b/benchmarks/independent/population-v3-php.toml index 1af8a4f..8433df1 100644 --- a/benchmarks/independent/population-v3-php.toml +++ b/benchmarks/independent/population-v3-php.toml @@ -44,9 +44,19 @@ # # Licences marked "verify" must be checked before any result is published. Kirby is under a proprietary licence: # it stays in the population, but its results are unpublishable without the licensor's permission. +# +# FROZEN 2026-09-30 (freeze.py, freeze-v3-php.json; git history, file lists and fix diffs only). No case dropped: +# both inferred fixes change a declared site function (pagible: proxy and isValidUrl; podlove: create_link_item, +# while GenerationGuard::__destruct is not in the diff). Sites resolved from the fix diff are marked in +# `site_basis`; a file the fix does not change stays unresolved (`site_files_unresolved`). Benign controls come +# from freeze.py's strict proposal (single-language population: PHP change, merges judged with everything they +# bring in, history before the vulnerable pin when nothing after the fix qualifies), reviewed on commit messages +# and file names; five proposals were replaced by hand, with the reason in `how`. schema_version = 1 -status = "reserved" +status = "frozen" +frozen_on = "2026-09-30" +freeze_record = "freeze-v3-php.json" language = "php" reserved_on = "2026-09-30" selected_by = "maintainer" @@ -80,6 +90,7 @@ privilege = "not stated ('remotely')" license = "BSD-3-Clause" confidence = "medium" caveats = ["VulDB auto-generated description; the fix message ('Prevent textconv') suggests git config/attribute abuse rather than shell metacharacters, and the patched lines may not be in getDefaultBranch -- verify at freeze"] +benign = { base = "ce5806c849f453d7200c1f488ee6aba19eef9a4c", tip = "ecc8e18e03d9b547e82b7c7c148cb00ba01c0cc9", subject = "Merge pull request #902 from zoredache/sort_repositories", how = "automatic proposal (before the vulnerable pin), reviewed" } [[case]] id = "qloapps-customermessage-bo-query-sqli" @@ -97,13 +108,15 @@ ancestry = "parent" fix_named_by_advisory = true sink = "database query in CustomerMessage.php built from the `bo_query` parameter" source = "`bo_query` request parameter" -sites = [] +sites = ["classes/CustomerMessage.php::searchByName"] site_files = ["classes/CustomerMessage.php"] +site_basis = { "classes/CustomerMessage.php::searchByName" = "resolved-from-fix-diff" } site_derivation = "the advisory names 'CustomerMessage.php' (basename) and the parameter; the classes/ path is from the fix commit; no function named" privilege = "administrator" license = "OSL-3.0" confidence = "medium" caveats = ["one fix covers two advisories (Address.php is GHSA-mj48); repo 129 MB, near the size cap", "function-level site to be resolved at freeze"] +benign = { base = "5c52b864878848a625a5d9cfbb6e3925ab853ad1", tip = "a8f6071627bf97a77d715c861c1157fded5e9e60", subject = "Merge pull request #1813 from Azhar-Webkul/gli-3095", how = "chosen by hand after review: proposal e43b4500 changes payment-status logic; next passing change taken" } [[case]] id = "roundcube-markasjunk-cmd-learn-rce" @@ -121,13 +134,15 @@ ancestry = "parent; ancestor of tag 1.6.18" fix_named_by_advisory = true sink = "the markasjunk plugin's cmd_learn driver runs a shell command with placeholder replacement values" source = "crafted placeholder replacement values (message/user data) when a message is marked as junk" -sites = [] +sites = ["plugins/markasjunk/drivers/cmd_learn.php::_do_salearn"] site_files = ["plugins/markasjunk/drivers/cmd_learn.php"] +site_basis = { "plugins/markasjunk/drivers/cmd_learn.php::_do_salearn" = "resolved-from-fix-diff" } site_derivation = "the advisory names 'the cmd_learn driver of the markasjunk plugin'; the file path follows from that name and is confirmed by the fix commit; no function named" privilege = "authenticated webmail user" license = "GPL-3.0" confidence = "medium" caveats = ["only instances that enable the markasjunk plugin with the cmd_learn driver"] +benign = { base = "1cddafded973c4bf6faa173accaf8f52ca1ac0d0", tip = "747f544708ed1d698b677d333346164a9451aeda", subject = "Simplify", how = "automatic proposal (after the fix), reviewed" } [[case]] id = "maccms10-template-blacklist-rce" @@ -145,13 +160,15 @@ ancestry = "parent" fix_named_by_advisory = true sink = "ThinkPHP `{if condition=...}` compiled into raw PHP; the admin template editor only blocks a function blacklist" source = "template content saved through the admin template editor" -sites = [] +sites = ["application/admin/controller/Template.php::info"] site_files = ["application/admin/controller/Template.php"] +site_basis = { "application/admin/controller/Template.php::info" = "resolved-from-fix-diff" } site_derivation = "file path quoted by the advisory; no function named" privilege = "administrator" license = "GitHub: NOASSERTION; no licence declared by the project [verify before publishing]" confidence = "medium" caveats = ["guard bypass (incomplete blacklist); the fix is still a blacklist", "licence not declared on GitHub"] +benign = { base = "6ace264920bc50a07944c163b17a8fd8a3f7687f", tip = "c9f8d0e848489a2dc73daf1106748d2cd94d8138", subject = "修复 meilisearch 排序规则语法兼容问题", how = "chosen by hand after review: proposal 668ba418 fixes a registration-API validator (input validation); rejected" } [[case]] id = "mantisbt-tokenizer-eval-rce" @@ -169,14 +186,17 @@ ancestry = "parent; ancestor of tag release-2.28.4" fix_named_by_advisory = true sink = "`Tokenizer` (reached through `ConfigParser`) calls `eval()` with a `return;` prefix; PHP hoists class declarations past the return" source = "non-string configuration value submitted to adm_config_set.php (Manage Configuration)" -sites = [] +sites = ["core/classes/Tokenizer.class.php::__construct"] site_files = ["core/classes/Tokenizer.class.php", "adm_config_set.php"] +site_basis = { "core/classes/Tokenizer.class.php::__construct" = "resolved-from-fix-diff" } +site_files_unresolved = { "adm_config_set.php" = "the fix does not change this file" } sink_evidence = "eval(" site_derivation = "the advisory names adm_config_set.php, ConfigParser, Tokenizer and eval(); the Tokenizer file path and the enclosing function are not named (path from the fix commit)" privilege = "administrator" license = "GitHub: none; GPL per project [verify before publishing]" confidence = "high" caveats = ["function-level site must be resolved at freeze by reading the file (the advisory names only the class)"] +benign = { base = "088fa767bce246b6d2f083f32d937f22c8de4767", tip = "d6ca719b591f70acfa711fc41705388557b6ac04", subject = "Improve Workflow Graph display.", how = "automatic proposal (after the fix), reviewed" } [[case]] id = "sharp-editor-iframe-srcdoc-xss" @@ -194,14 +214,16 @@ ancestry = "parent; ancestor of tag v9.22.5" fix_named_by_advisory = true sink = "Symfony HtmlSanitizer config allows the iframe `srcdoc` attribute; the value is rendered as live HTML" source = "Editor field content saved by a user with edit permission" -sites = [] +sites = ["src/Utils/Sanitization/FormatsSanitizedValue.php::sanitizer", "src/Utils/Sanitization/FormatsSanitizedValue.php::isEncodingNeeded"] site_files = ["src/Utils/Sanitization/FormatsSanitizedValue.php"] +site_basis = { "src/Utils/Sanitization/FormatsSanitizedValue.php::sanitizer" = "resolved-from-fix-diff", "src/Utils/Sanitization/FormatsSanitizedValue.php::isEncodingNeeded" = "resolved-from-fix-diff" } sink_evidence = "srcdoc" site_derivation = "file path quoted by the advisory; no function named" privilege = "authenticated editor" license = "MIT" confidence = "medium" caveats = ["the flaw is a sanitizer allow-list entry, not a missing escape call"] +benign = { base = "ec509a22c808a5bd9dfad6a0a85c92ce6f411e21", tip = "7c601fffe930fc321a5d8bcb25cb072d50a6fc08", subject = "Update version to v9.22.5", how = "automatic proposal (after the fix), reviewed" } [[case]] id = "privatebin-jsonld-request-uri-injection" @@ -226,6 +248,7 @@ privilege = "unauthenticated" license = "GitHub: NOASSERTION; project files state a zlib-style licence [verify before publishing]" confidence = "high" caveats = ["the fix changes Request.php only; Controller::_jsonld is a declared site outside the diff", "response type is JSON-LD, not HTML", "licence NOASSERTION on GitHub (zlib-style per project, verify)"] +benign = { base = "75f056dcda955d94c17ec5a4f8c54a9b7bfcee07", tip = "3035d6e5d3cceb7f06f7b7bb80968727879c52b0", subject = "incrementing version", how = "automatic proposal (after the fix), reviewed" } [[case]] id = "openstamanager-group-name-html-injection" @@ -251,6 +274,7 @@ privilege = "authenticated user able to create groups" license = "GPL-3.0" confidence = "high" caveats = ["second advisory commit (7c9eafe9, global GET/POST filtering) is not in the pinned pair"] +benign = { base = "0b88ad1963871abfa5e134004ca0c74c496d8d2f", tip = "1832b85d0e570e0b873c7c23dcfcb6b5d1365a28", subject = "fix: ricerca riferimenti in importazione fattura per riga con articolo non esistente", how = "automatic proposal (after the fix), reviewed" } [[case]] id = "wp-geo-my-wp-info-window-lfi" @@ -268,13 +292,15 @@ ancestry = "parent" fix_named_by_advisory = true sink = "include of a file chosen through `gmw_posts_locator_ajax_info_window_loader`" source = "AJAX request parameters (unauthenticated)" -sites = ["plugins/posts-locator/includes/gmw-posts-locator-functions.php::gmw_posts_locator_ajax_info_window_loader"] +sites = ["plugins/posts-locator/includes/gmw-posts-locator-functions.php::gmw_posts_locator_ajax_info_window_loader", "includes/gmw-functions.php::gmw_ajax_info_window_init"] site_files = ["includes/gmw-functions.php"] +site_basis = { "plugins/posts-locator/includes/gmw-posts-locator-functions.php::gmw_posts_locator_ajax_info_window_loader" = "advisory-named", "includes/gmw-functions.php::gmw_ajax_info_window_init" = "resolved-from-fix-diff" } site_derivation = "function named by the advisory text; files from the advisory's own trac reference links (gmw-functions.php#L1945/L1987, gmw-posts-locator-functions.php#L964/L975); the function-to-file pairing is an assumption to confirm at freeze" privilege = "unauthenticated" license = "GitHub: none; GPL per project [verify before publishing]" confidence = "medium" caveats = ["WordPress plugin; GitHub shows no licence (plugin header GPL presumed, verify)", "release commit (small)"] +benign = { base = "9c394482d1509ca9222365a91117a10b19619be8", tip = "85bcd85541c777b6fd8580cb1915504a2a2b84cf", subject = "Release 4.5.5.2", how = "automatic proposal (before the vulnerable pin), reviewed" } [[case]] id = "kirby-realpath-sibling-traversal" @@ -299,6 +325,7 @@ privilege = "unauthenticated" license = "Kirby licence (proprietary, GitHub NOASSERTION) -- not OSI; results unpublishable without permission" confidence = "medium" caveats = ["only deployments whose index root has a sibling directory with the same name prefix", "Kirby is under a proprietary licence (GitHub NOASSERTION); must be cleared before publishing"] +benign = { base = "7ee5b79dd8eea6aa3d5a7eb0c5411a6631af88f1", tip = "d333470b9d9df7a48ee8d0bc2249db36aa9dd68c", subject = "style: Fix coding style", how = "chosen by hand after review: proposals 65dfca05/0901d0cb concern the content salt, 7bbefc1e updates vendored dependencies; rejected" } [[case]] id = "mineadmin-appstore-identifier-traversal" @@ -323,6 +350,7 @@ privilege = "authenticated admin with app-store access (verify)" license = "Apache-2.0" confidence = "high" caveats = ["Hyperf framework (not Laravel/Symfony)"] +benign = { base = "c57753e3f547683dc3e14836c3563a51ba4edaee", tip = "ff33969ef5ee378654dacab89e6f4b1928f4edd1", subject = "Fix/service logstash namespace (#663)", how = "chosen by hand after review: proposal 15deedde is a framework/dependency upgrade; rejected" } [[case]] id = "pagible-admin-proxy-dns-rebinding-ssrf" @@ -338,6 +366,7 @@ vulnerable = "03f03b2f81b20b1c1625ff227235c43791ddbb33" fixed = "09a8205d513ec89ed22cdd7bdae0f4c181cee082" ancestry = "parent; ancestor of tag 0.10.4" fix_named_by_advisory = false +inferred_fix_confirmed = ["admin/src/Controllers/AdminController.php::proxy", "core/src/Utils.php::isValidUrl"] sink = "`AdminController::proxy` → Guzzle request after `\\Aimeos\\Cms\\Utils::isValidUrl($url)`" source = "`url` query parameter of `/cmsproxy`" sites = ["admin/src/Controllers/AdminController.php::proxy", "core/src/Utils.php::isValidUrl"] @@ -347,6 +376,7 @@ privilege = "authenticated (proxy token)" license = "MIT" confidence = "medium" caveats = ["guard bypass (DNS rebinding)"] +benign = { base = "981b8d63e52e7ba8875a9a52c1865eb3bc662154", tip = "d7577efd68e9c5e36436c8572bfed0df2be2c634", subject = "Publish related items (#210)", how = "automatic proposal (after the fix), reviewed" } [[case]] id = "wp-graphql-updatepost-publish-bypass" @@ -364,13 +394,15 @@ ancestry = "parent; ancestor of tag wp-graphql/v2.22.2" fix_named_by_advisory = true sink = "`updatePost` mutation checks only `$post_type_object->cap->edit_posts`" source = "GraphQL `updatePost` mutation input (status PUBLISH, published post id)" -sites = [] +sites = ["plugins/wp-graphql/src/Mutation/PostObjectUpdate.php::mutate_and_get_payload"] site_files = ["plugins/wp-graphql/src/Mutation/PostObjectUpdate.php"] +site_basis = { "plugins/wp-graphql/src/Mutation/PostObjectUpdate.php::mutate_and_get_payload" = "resolved-from-fix-diff" } site_derivation = "the advisory names src/Mutation/PostObjectUpdate.php; the repository is a monorepo (plugins/wp-graphql/ prefix from the fix commit); no function named" privilege = "Contributor" license = "GPL-3.0" confidence = "medium" caveats = ["fix commit also carries an unrelated Model.php change", "function-level site to resolve at freeze"] +benign = { base = "b583bb6ec87016965085afc7ea586f114c711c68", tip = "cc12aed380414545a005048bfcebe4ba50e9c8f9", subject = "fix: point acf.wpgraphql.com links at the wpgraphql.com docs portal (#4251)", how = "automatic proposal (before the vulnerable pin), reviewed" } [[case]] id = "sylius-mollie-thankyou-order-idor" @@ -395,6 +427,7 @@ privilege = "unauthenticated" license = "MIT" confidence = "high" caveats = ["merge commit; one fix covers two advisories (webhook forgery GHSA-rc52 is the other)"] +benign = { base = "19f22cd673fde2b6ac750326fecbfe5fc4c06b9e", tip = "91254cc9c9821acbb3c785918759feedd827bdb9", subject = "Add support for Swish gateway (#324)", how = "chosen by hand after review: proposal 45b01569 is a branch upmerge; rejected" } [[case]] id = "wp-podlove-generation-guard-poi" @@ -410,12 +443,15 @@ vulnerable = "4eedbd66796942ab052de2dbd6311a24cc75c4a7" fixed = "ea878175f2612d54888d9e2aa49ae4db93b724fd" ancestry = "linear chain of 3 commits (4eedbd66 → d49c4d7f → b59d7962 → ea878175); ancestor of tag 4.5.4" fix_named_by_advisory = false +inferred_fix_confirmed = ["lib/modules/shownotes/rest_api.php::create_link_item"] sink = "unserialize() of model data (lib/model/base.php#L552) instantiating `Podlove\\ImageCache\\GenerationGuard`, whose `__destruct()` calls `wp_delete_file()`" source = "shownotes REST input handled by `create_link_item` (contributor+)" -sites = ["lib/modules/shownotes/rest_api.php::create_link_item", "lib/image_cache/generation_guard.php::__destruct"] +sites = ["lib/modules/shownotes/rest_api.php::create_link_item", "lib/image_cache/generation_guard.php::__destruct", "lib/model/base.php::to_array"] site_files = ["lib/model/base.php"] +site_basis = { "lib/modules/shownotes/rest_api.php::create_link_item" = "advisory-named", "lib/image_cache/generation_guard.php::__destruct" = "advisory-named", "lib/model/base.php::to_array" = "resolved-from-fix-diff" } site_derivation = "create_link_item and GenerationGuard::__destruct named by the advisory; files from the advisory's own trac reference links" privilege = "Contributor" license = "MIT" confidence = "medium" caveats = ["fix inferred from file history (three commits)", "the advisory text describes the impact (file deletion) under CWE-502"] +benign = { base = "af187ad4bba2739839a0aa1e6cf2cabd6f9b99f5", tip = "50da8db0f5a85afaf27cd6199940be0403b368ec", subject = "fix PHP warning", how = "automatic proposal (after the fix), reviewed" } diff --git a/tests/test_independent_freeze.py b/tests/test_independent_freeze.py new file mode 100644 index 0000000..daa132e --- /dev/null +++ b/tests/test_independent_freeze.py @@ -0,0 +1,59 @@ +"""freeze.py resolves an advisory's file-only site to the function the fix changes (population v3, decision 2). + +The resolver reads PHP text only; it must not be fooled by braces in strings, comments or inline HTML, and a line +outside every function is ``.""" + +from __future__ import annotations + +import importlib.util +from pathlib import Path + +SPEC = importlib.util.spec_from_file_location("freeze", Path(__file__).resolve().parents[1] / "benchmarks" / "independent" / "freeze.py") +freeze = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(freeze) + +PHP = """{ not code } + +
{
+ None: + spans = {name: (first, last) for name, first, last in freeze.php_functions(PHP)} + assert spans == {"open": (6, 10), "close": (14, 19), "helper": (24, 24)} + + +def test_a_line_resolves_to_its_innermost_named_function_or_global() -> None: + spans = freeze.php_functions(PHP) + assert freeze.enclosing(spans, 9) == "open" # inside the closure: the named function owns it + assert freeze.enclosing(spans, 17) == "close" + assert freeze.enclosing(spans, 21) == "" + assert freeze.enclosing(spans, 1) == "" + + +def test_an_insertion_after_a_closing_brace_is_outside_the_function() -> None: + spans = freeze.php_functions(PHP) + assert freeze.enclosing(spans, 10) == "open" # a changed line on the closing brace + assert freeze.enclosing(spans, 10, inserted=True) == "" # new code that follows it + assert freeze.enclosing(spans, 8, inserted=True) == "open"