From 9fac7766baa15031d4efa88b1864192ee12ad51d Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 12:14:20 -0400 Subject: [PATCH] wesnoth: 3 CWE-407 defects, MOAD 0002-0005 CLEAN wesnoth-0001: A* pathfinding std::find on pq vector for decrease-key O(V*Q) per relaxation, fix: lazy deletion. HIGH, 1279x at N=5000. wesnoth-0002: server ip_log_ deque linear scan on login/logoff O(N) per event with N up to 500. MEDIUM, 437x at L=2000. wesnoth-0003: combine_special_notes O(N^2) vector dedup utils::contains on vector per note insertion. MEDIUM, 499x at N=1000. MOAD-0002 (Intertangle): singletons deeply embedded, not actionable. MOAD-0003 (Leaked Context): thread_local for debug/call-stack only. MOAD-0004 (Logged Secret): passwords never logged verbatim. MOAD-0005 (Thundering Herd): single-threaded game + coroutine server. 6/6 unit tests PASS. --- UNDF-REGISTRY.json | 3 +- defects/wesnoth-0001/patch/wesnoth-0001.patch | 46 ++++ defects/wesnoth-0001/test/wesnoth-0001-test | Bin 0 -> 22552 bytes .../wesnoth-0001/test/wesnoth-0001-test.cpp | 242 ++++++++++++++++++ defects/wesnoth-0002/patch/wesnoth-0002.patch | 63 +++++ defects/wesnoth-0002/test/wesnoth-0002-test | Bin 0 -> 35896 bytes .../wesnoth-0002/test/wesnoth-0002-test.cpp | 174 +++++++++++++ defects/wesnoth-0003/patch/wesnoth-0003.patch | 67 +++++ defects/wesnoth-0003/test/wesnoth-0003-test | Bin 0 -> 27432 bytes .../wesnoth-0003/test/wesnoth-0003-test.cpp | 141 ++++++++++ 10 files changed, 735 insertions(+), 1 deletion(-) create mode 100644 defects/wesnoth-0001/patch/wesnoth-0001.patch create mode 100755 defects/wesnoth-0001/test/wesnoth-0001-test create mode 100644 defects/wesnoth-0001/test/wesnoth-0001-test.cpp create mode 100644 defects/wesnoth-0002/patch/wesnoth-0002.patch create mode 100755 defects/wesnoth-0002/test/wesnoth-0002-test create mode 100644 defects/wesnoth-0002/test/wesnoth-0002-test.cpp create mode 100644 defects/wesnoth-0003/patch/wesnoth-0003.patch create mode 100755 defects/wesnoth-0003/test/wesnoth-0003-test create mode 100644 defects/wesnoth-0003/test/wesnoth-0003-test.cpp diff --git a/UNDF-REGISTRY.json b/UNDF-REGISTRY.json index bf826dfe0..1e00146ab 100644 --- a/UNDF-REGISTRY.json +++ b/UNDF-REGISTRY.json @@ -969,5 +969,6 @@ "openttd-0002-0002": "UNDF-2026-000000968", "renpy-0001-0001": "UNDF-2026-000000969", "wesnoth-0001-0001": "UNDF-2026-000000970", - "wesnoth-0002-0002": "UNDF-2026-000000971" + "wesnoth-0002-0002": "UNDF-2026-000000971", + "wesnoth-0003-0003": "UNDF-2026-000000972" } diff --git a/defects/wesnoth-0001/patch/wesnoth-0001.patch b/defects/wesnoth-0001/patch/wesnoth-0001.patch new file mode 100644 index 000000000..1063cd365 --- /dev/null +++ b/defects/wesnoth-0001/patch/wesnoth-0001.patch @@ -0,0 +1,46 @@ +# UNDF: UNDF-2026-000000970 +--- a/src/pathfind/astarsearch.cpp ++++ b/src/pathfind/astarsearch.cpp +@@ -172,9 +172,16 @@ plain_route a_star_search(const map_location& src, const map_location& dst, + std::vector pq; + pq.push_back(index(src)); + ++ // Lazy-deletion A*: when a node is relaxed while already in the open ++ // set, we push a duplicate entry instead of performing a decrease-key. ++ // Stale duplicates are discarded when popped (the node will already ++ // have in == search_counter, meaning it was finalized). + while (!pq.empty()) { +- node& n = nodes[pq.front()]; +- +- n.in = search_counter; + + std::pop_heap(pq.begin(), pq.end(), node_comp); +- pq.pop_back(); ++ int front_idx = pq.back(); ++ pq.pop_back(); ++ ++ node& n = nodes[front_idx]; ++ ++ // Skip stale duplicates from lazy decrease-key. ++ if (n.in == search_counter) continue; ++ n.in = search_counter; + + if (n.t >= dst_node.g) break; + +@@ -213,10 +220,10 @@ plain_route a_star_search(const map_location& src, const map_location& dst, + + next = node(cost, loc, n.curr, dst, true, teleports, srch, dsth); + +- if (in_list) { +- std::push_heap(pq.begin(), std::find(pq.begin(), pq.end(), static_cast(index(loc))) + 1, node_comp); +- } else { +- pq.push_back(index(loc)); +- std::push_heap(pq.begin(), pq.end(), node_comp); +- } ++ // Always push a new entry. If the node was already in the ++ // open set (in_list == true), the old entry becomes stale ++ // and will be skipped when popped (lazy deletion). ++ pq.push_back(index(loc)); ++ std::push_heap(pq.begin(), pq.end(), node_comp); + } + } diff --git a/defects/wesnoth-0001/test/wesnoth-0001-test b/defects/wesnoth-0001/test/wesnoth-0001-test new file mode 100755 index 0000000000000000000000000000000000000000..ac35d24747885170d3416e61d5dde8d1b2a7b85b GIT binary patch literal 22552 zcmeHvdwg5PmH(BkB+8?{3Nh3W3W6%sro^#q=b?mvmB`5`krOv@AV5G-BsmuNg)Aiw zCB(sY;35Pm3zY6cH@oe&Y`edb?zT&sQt%@$XaNT(%@RsYAnjEgNC>Yqgy{D@b7y2( zu}gpb>>vI7KAF!)I(N>TbLPyMGiM%GnoX771vw^@B2%t%jY7G$3k*z!C@82F4*(U) zEagl1wkek?qmegroKaq30F)Z(K+aHT5%^p{;wzwsVc^9ET9Hs{NRaqON_At65(yQn zkte=U^eFP#aOx!norJPHU0;b&u4KWBM12xw@{w&;&NcW}Rt<$+!Y=y+lzfyWzB<8I zC-@|^3VI2pJ;^5YnI-I*4YSl%via%+Up90@CM5}}4ob@pM}vP$ebs`mq|A_$4WBX4 ziiEPhZ-9^N^4BKW&C5i6BlW|R!k!XJKbuw`UNfV7T77V8eYi2!I<HC6NLn;Jtk{x$U>qipz+8<$5*OMJd)U8HHfuRhec zHd^NkMIud+>cs)YcVn==dR+rm)!OQ74nXP3wpULm?m<(%=s_0uyNU z`)b2f*&QJTgn{M_3aDD5!NAwQPJ?6yW!DD4MmY!N0tSBJ{Q17J;u2;4%9XBp9-pJQ zEaS1*sVww*=AjHU<;JtGG~>xJh%6g?EG?d%rX@b497MxhjLy-3V-W3fm9ZEzX`2$N0JjD_jLkQEKfv316lb(Tx0n+Js1|AJf zr;{1@yfhYe75Y$f#%18Q3i>g?iKg<>$1?B~17+HlftRtD%AU-?^Lbw|Y|p?O@zW^S zk%6bWW$MbnOWRS|z6`ur0&teT4EzKs6;@jLCa^wBCzo4C`yTK^3eK`h>3 ze+*3svag$`6yhOfVcH+`ZZt@L9ACs!sJ}v#;HlOh6Fm2x&66OWI(&JxPxU@d=eDy! z!IzU@Dkn*W`cKZHh`yy4p%}*NfONxtuIpK1dfBCl@*H)f>pIs8SIvs$@RF557B@6z z8me)0vBWC7O~3aH$)E0$9F8tM22`zjtm_w7LIQNyNo%}kgZNS>y6uH*uiHKg8AV@( z^{F>eZ@2CYAnW)9v|cT#{}wLdO$6-)ndrCmOCaWiYW3Q!_?}|1Q7;#EpHNr1?FF@J z&8Q_DGp%3U@;j(bWxNSH$U&iD-B(cK8=u2kiKxAhO!frUswjBt646+wz7yqK^=}dr zRHtqGvwiTvEY<%7M`X}-=gTPKitCq>*4o>H7^dykZ^iNu+SYKLr`Eto9&ImF>oL?m zp-!x^TS)nDp{Q1!rmI{2i7S8Xc~XOEM|c}fF!jn8Sz>ArPQ>UL1@Ak$PN=u+gM^kV^Shr}Dldme57 zElfMa+TT51T~q1kay$=#w}UQq{v4;Gd{kR3;}h_HU|3?*jRc%0qvvDlx`duFb&XB+ ztCzMxEu~jL&^tK&^eYJzonhK>*8TpqtovjxGwosf&qVVf;5|em5@1R78}o?drv|jm zrD7%a^%bmr?!VF_(+)=`u=w0P$mkpX45J63>z+}&kvH88-gN!MpInbZp%3L<*QaCZ z=A%zSoK|CJZ08CijbhrJ_7W{>_cG0EugD0w#;k#;8yLf!J?h;D5pF1wHbVqCdY4+{ zwy#Dr)Q)dcQDQ;`OI-XSm16wqmqAZB?WphY?wn9SF?==CdZ?P_S~b6m=J&nxtWY+^ z+ItGriSzWm^GU)C@RoiMfgekUm*8{x^lh+jjiw` z%zNY2`R%8IY6lH82v*egL;NYf%dItA$pnF1qIK2YC^SyxwV}ZxHTkV() zE#l)n?Yqq`t?IN_6J4skPqM92A7KO%N77f4N@N4lW*cTs){ZJos44cS^oAPziRkoCZA zVVif+c+3p8?Oo>NfAQ8)XOWRD$?pxxPe}g5V4migq8`?L3Qahbqjo&Uc~0f39dx1% zoGCUgK533s?YMWM7_jL;v7;;Dq9Wbz1fh%5hI{9y0 z1N4h4o^L0N;}fs;Cofr=%xZIKD^6pm(=dT*`a0gvz$a;S5TE3PJ}+Sbf!3Kxw;u7F zT#i@}pENKaZI6+_&EE;OZxA->KU8eeS~0#{QM zD;W}BDd-Hw*CdTWsjZB!)jYoboQksIYxjpne05?ygEAgpt3`ZW{a4151ye7!m|z9RZ(1lK~E0h;ySQS^tKw}I1(j;N+q6d;4#ofr)9dV4Fmg|&8TmGg78 z!wUb0<9gcPGkc4$9i(vC0-PtX0(cO^fD^b1-G?Z!UeLZVKN#JWj;2e|CnDCW_w=F~ z@@pH*twu1t$Qxf{p-AzFmo_s4;|>4jyeu$D1k?Owo<#oZ?RP-zZ3v?|Eg?vlz>PLB zN1JB_CCRjs5(uTQ^H9pe%x9=BGt5xacq6sYxvKLMBigLSir8E9iQa`0>IU+v z^p;HR{HZWd0iq3gXGXNC-Rg>e8N(NQAGf`l#&8NFm->eKg+}f(*c&H<&`+*!&{hO= z9w++Ylt$kU>3iyE9wgosqmLiG8>4SE>c>`_hI_W6G4%M{kF6MOJC0*S=Atoj$lZkJ zjP(qQkD;{_)=}#bZT7OFJ;~i*;qyW7;LzlGF5+zZNB$DMtHz_9#s)Or1b@1eIe#C$ z5>C`Xs>9RfFhqGQ##*fCW8Diu=+y#H#oqD6FSfJJgVBpT)L&Qwv7)!g+Q}9yrW8J{}Wh ztO9AC+&dp8B!BD0f@;3boV~FDk9La%%ld+ho|o;B>rRjMW>pbp&3+-ikL^gW4~zOx zmwNXh5<7^+uw>5NOkJ>>IZs5+hbHG`#5`voQy2Dl6IWHx>~HaOAI|k^$Mi29qn-zX zl=f&{ixOrV#@IT1`h@-!LjNx4UjhC5Vgp|7q*v?lIR6m+%7-d-3bSJ7C@>a{i*%+f zI!Wz(hvwf4J@IvXsK5;^Gzb6aeHt$`*(Tf#N|iPm%c!m@lkU+LSgN!IkTk?FaKcsC zr0Zp1!+Ot%?;h=UY$vT#kuFUyrrVDec;jm=-uN`w&0D1Nb&Io4?Wmynoo1KMJ=!PU zq9Zhgb-Y66OH9BXqB|0%HCHEv630Qu^RD<9jK7KIs-lmUCa$(==83HHl}!y;R@`*~ z-y&jMVl9hJ_p|Sj`6k$_I^T;0nTBzzZNyp5&Do6@90cr_m^mqJB6rmC?fQLOM!ubC z-OSWGaJQKMwU;sVdo{CN{|*so-Kc((i^XFw<}s?=TlBtui>QB~UHT9D!4&yN_6wtu z6h_NFru`aT0Y7~wXW%{-zZ&ahY;&=OM<3{?k5UV~cFVx`*^WV`Zg|T+tf&((iIV&V z3eXSlW5cTd7z~5WF~&<`K?*xKm-Yh9cHKv=Wu_js|CHDH(p^oSI6_hZ3v|m+>g}}* z6b=f|@>pUKn)f=>_OWgqtozvhlOE^(jZ$L9my>9IS zIbOWl9?B8%fIcXm>zA?L(q2vb%@`bL!CyAPU%o~TT|`684F6U;Jchq`gufiBa-LQ@ zP7V5tdiVdK4Ec;1q!csQB&Ob)Wz1jy}t&413^tRX3osO4HSQ%oW z-rj@VLB(6@3b~K<9Y*Xu)xg=`;O?0Wo0U0W_S*1O8+XU~zR* zx>T#giO9_skJgawei%3GR|t8O;CFBz969L|!3ovJvp8}X`^>bP6v1f{l=-9DIS16q z5WZ+%$o*<(+OMzy-3O?4>gH9B+ z7b&d2wt{IN)6t?eV;u&+q@%@oaEo5nJpiBRW&1y4&bKyJSH%lF+KB8I5AIyUpYVA z)b09Ymnn9Er~POS54HNWUQ<8(5AiiRod%y~a+cWS>{nyE}#)}6#y7)|%c&>mJ_F50O@8&+uh!8VxJyh`;)BfzSTHroAQV4mhUhJj6V;Wo{FLBrn!?aI02_m2J|G_T5*_$ zn0^qbsRc&S6dNEqJO`gen16b#czEc*Nty}|zJw1>B?r2}un*6>Yt@$hCP;Vzh*xXX z&-#FT4bMHmb>nCNp4$`qsVB~B^2Dv4y=`r%lhWoxT=6M*Y-3~lFpgylj(EzpX0)Ks zqjC0(3D{U4+(V+4Gwm}rw+V}y^_W+yO)PHiW!gVb(M^zvKr>!@g*kA3(Z{;`$4qIv z?y4u4gQ{cQht$oO9`_uR#rk}94&(4?IdO3-?84hjs^D6)b0%&s?rX_9Y0_0 zDcTLs+X2E9w$Lp#JTCIZ=6BH~KJU;G$T9snPyo8LUAmh;_HZxiw=q+n`cRMh->-lx z@)Bl`*z=|XX-}d4)+d3XDiJby)TeeKwz-}CEnjnM{f<{YJ>dHEBbWAD=l+%pYPV7~ zIy6a>>5-#GsI+BB91M8+FF~A9V$7H6DRT2s`b7(CpI}$m=A%1synzU4<;(l{^%j@* z8y5J4{X(vM&;c34vcvu)j7EpbC$*Ujix4_OQ=jVQi;xxs7C-<{F@e6qR|Ag*fU@|WL=c-PSaWZb_<(xJ9f+c z&;=s{C)2Eb58~%7$C-A!#cMj|#!?IC59=)kE1R?48odjYL4y)!BUn?_f_l9rJ|}T~ z%K6)k>s&PJ|4IJ{%LLM1n`}51Mi8}u?LWfC_E3ZI`e0>Oax_Yfbs8UJ!;y>w$)JDG z0iBg9)(>Iei(2oz59_fBu(kTs$-n<($8a(=3IJdB1_X&(>H zhw+VAm|#Oh>jc^m&3OmG1N~j)JQl;Uuf-allk|a!YWQcI6C^j1UWrW>Y}@oRC&(v~ z*P>iM4~)k9K>mKXUA_?}Yn)5}zgWP8-xRJ31)@!n*|UB03)NbGtUl`VH#dhGgPA3f zkiWjZDc}n?(oaMc{AkohKL#xw(0misjM%JYeh9O<0u)1!cjsb<6QOiHqyF+IQvnQZm`$l6fE7OnF)!UCuCz7#qjhA1aN}A- z7B@&!qpkUNTjBicE2oy1%q$v571ux$n`1VN6^Xz;jiHtn8|YdZV13vO#<#7F_?zpv zlA!0B`PZ}*l68GS+f;65Uu}`?YMUc874N)^G>!`{oo&O+Z(pI=+ zZYetPDqH1Ur6toEn}Q+OM#)buo6~}M& zfZGAv04s3t{2PKe3AZ7nul_idDhKQXtOneUw;tO8DbbG^D4Fh9rkGl-rpXh=Shhko z;V;Ig8Ef||h`>FDpZnPHSdP!hgQ?UuKqb$*Ag^GNI)1&SO}X|W=apsl%Lz^VtMF-t zO*kVVkq95frw;h7gb;~n?#HJKo6yTLaNF?wW(JPx{uw?efeQgD3-YY@=eY9p7UWgsG-us7@wRk+O)Rpa!@i>mY9>FsulD`YM zLioZ1Y5BZe!^!^!+I4VP`R?&b1!U3xi0W}P@G+iA*9XW(39`}sW_MoUmQn7!lJ?Q= zyo#J18^iu7mHJuSJF2CL`*+ zFt6W~yES)MtzltBvEkE-&-dSep97jxFa($~dx+|N9G~NdQ>ie3elq%namwy-cm~dVr2%9r`&Q$#*t{@8TTqI@nnCIfdaowL9>3!JmS-(~^%e=Pa`D=`)D z|CEtLnKy|CnWz7ZQNl5=k!V6e!YeSIY{dq|9LtmZAJ}A(w+M*ec=06v4@PS{O0xXp zGpQ!ZZ^zs~Nd`Pxr&7YX3{M9{x!eeB6#CJ8NQve)O8CvgkR;fYR`Gu*gm#_|2$(M* z&ewPne^XPo3I!#8hj@^BOMbjj;7QhxAARy9>2Scq+XrWs*r6b-|go|j8KF!KXLl^aqn#Q(1W`G4+gfhD59jkm7YD~SAR0h>a zbGEH;#hO@SG-h*@6_*v4Oq~(q+0wg8%Zp3OiycLxK=KR+$z9CK2ekhhlBoYoD7jyB zfK8O*7;IR2+F<7>R%P`>gOJvJl$PN`_=mIb>jYjAJM(_zDY>B#(eD%VwAP@s1o+|f zyg|@!rww05x=G;sv+(qbJd!WRy=3VC-lR-cRb$96GnTs-%jB83)ACV(q9hz zaR0BK1N`N%PxIFdO1VaUUEnKh2L3W3=wslm%0+3r3AA#W%ZD5hIB0J{$p(Mo@@+h; zNIBOdj@TfFFNlpI{_O<)MAEdxK++PK5(EA`)Z8ZmC+#69trhaOUvA|29uJSb0>8~> z;6nybKIC|j$pCq%h~!&^k!T%FX%fd9&zawIfuERjk&-=57XeT0?H7K|*Z5!zj38&D zps%nQ(xrVK2mUh1m;cL^adZdC$(g9gc}ME>>B$WSS+oP@z25 z7^X8&oMq$46!$Si!fRqUUvBh-D+Bc&P?R;`GKElMW5^d>&+lvS;p&BOW60CsZ73_P z^ft^WEys76b0&Q|@Kri}#?10rWiuSpk#P{Fywq9loH4W1Nf@Flol!Q!SvqS*c?ppa zw!EaYbY@w}%;_Z3F^hjWO3Ta2N~f1qB88XL_?9hRvb@CSC~5Xb{0+Y7hUQRZakwSo z$ALDTzZ-3bEnA|o+S=lPGI+;?FWTS>&@B@!FmkZTx3<1%jlbR(#94of&mU`50!at9pFa}uZ-4=!kqt^MlnD8Pv4(~XV9ChAmSMa$$RPFk7A$jBRr)HI zxas~2A8s`9RWgBO?q!P4eZvx0m1n-9Yp;g8@t?xaZ}#x4@W%VU?sJh2nm)E z1J``WwIS&)rOP{vYeoLjWhVx?xEBP@+S2613z#6?o^s=o%z#i%j#wEE-wFrHxXxrq zbwe~C+D)XW`}*QYsJ3|cYgK4SKr~%wl5r;ZAUECT!@EGobe{+Q=A-Du zJU9BGtrmisPH6Q1teqSU+*7$3=e%-TS*rIQG7An62S&9aDBctEiD2e zF7!#OKm%_$AJ4GfU%s|z$lXE}@?sx|9CGo|kVc&yB8H7dJa~L@Gy<6sUW|A|S1b*C zjgnH_vY{dBUjrD87_d%0VGR(9G%Lk;+Z!rI=qqlHG~pd-bVCMeO)QLyz`{X+!o5XP zX~DwD>-;TsN^x*QBdRc9G-8yj!x{pMiXj;v$|9k9KQV}>=K82o%zdF4kHu@7kfn!~ zP(Ue;hFX#3F{e1v#AlDw=IbbEpQ_ErnVGq2?%-Q*^Hsmuk$(h79?P%KZ&t zh&KenMHH+f8X7Ri53>~fn^1HfWx@yBG@c5?{z*c49wmx7t$Zfcdlu(`n%K9b0Z2z|f z|2!d3o-0b2$xmmg+5Gn}pA14PeP9#XgtCFV7Dol;_!!K70J_ z7W}gR<@t<+C1N~C{_Of+%;J~lSrR@Y83oL)|6msX@iK!@!mca^=|58J;Vk~`vy2i6 zZyvp2b0JX8;Fsr%tLGXON;)Gq=>HN=?;*(dW&QHp zPu@#PzZ;-LrN!{1VddX)^iK=OPsblN{$#gyfU? zOTbJxS-(6dDX1_gvgxU=%=A@Je@P}1&ys&?7GCma!nE&Z(6S06Qz8Njy|;e*uw>=v4p! literal 0 HcmV?d00001 diff --git a/defects/wesnoth-0001/test/wesnoth-0001-test.cpp b/defects/wesnoth-0001/test/wesnoth-0001-test.cpp new file mode 100644 index 000000000..38e8e259d --- /dev/null +++ b/defects/wesnoth-0001/test/wesnoth-0001-test.cpp @@ -0,0 +1,242 @@ +// wesnoth-0001-test.cpp +// Unit test: A* pathfinding std::find on priority queue vector (CWE-407) +// +// DEFECT: In astarsearch.cpp, the A* inner loop calls +// std::push_heap(pq.begin(), std::find(pq.begin(), pq.end(), index), ...) +// to perform decrease-key on a node already in the open set. +// std::find scans the entire pq vector, making the per-relaxation cost O(Q) +// where Q is the priority queue size. On hex maps with varied terrain costs, +// the same hex gets relaxed multiple times, triggering the linear scan. +// +// FIX: Use lazy deletion. Always push a new entry on relaxation. When +// popping, skip nodes that have already been finalized (in == search_counter). +// This eliminates the std::find entirely. +// +// BUILD: g++ -std=c++17 -O2 -o wesnoth-0001-test wesnoth-0001-test.cpp && ./wesnoth-0001-test + +#include +#include +#include +#include +#include +#include +#include + +static const unsigned SEARCH_COUNTER_BASE = 100; + +struct Node { + double g; + double t; + int prev; + unsigned in; + Node() : g(1e25), t(1e25), prev(-1), in(0) {} +}; + +struct Comp { + const std::vector& nodes; + Comp(const std::vector& n) : nodes(n) {} + bool operator()(int a, int b) const { + return nodes[b].t < nodes[a].t; + } +}; + +// Dense random graph to force many decrease-key operations +struct DenseGraph { + int n; // number of nodes + // adjacency: for each node, a list of (neighbor, cost) pairs + std::vector>> adj; + + void init(int nodes, int avg_edges_per_node, unsigned seed) { + n = nodes; + adj.resize(n); + std::mt19937 rng(seed); + std::uniform_int_distribution node_dist(0, n-1); + std::uniform_real_distribution cost_dist(1.0, 10.0); + + // Create a connected graph with many edges to force decrease-keys + // First create a chain + for (int i = 0; i < n - 1; i++) { + double c = cost_dist(rng); + adj[i].push_back({i+1, c}); + adj[i+1].push_back({i, c}); + } + // Then add random edges + for (int i = 0; i < n * avg_edges_per_node; i++) { + int u = node_dist(rng); + int v = node_dist(rng); + if (u != v) { + double c = cost_dist(rng); + adj[u].push_back({v, c}); + } + } + } +}; + +// DEFECTIVE: uses std::find for decrease-key +long long run_defective(const DenseGraph& g, int src, int dst, double* out_cost) { + unsigned search_counter = SEARCH_COUNTER_BASE; + std::vector nodes(g.n); + Comp comp(nodes); + + nodes[src].g = 0; + nodes[src].t = 0; + nodes[src].in = search_counter + 1; + + std::vector pq; + pq.push_back(src); + + long long ops = 0; + long long decrease_keys = 0; + + while (!pq.empty()) { + int front_idx = pq.front(); + nodes[front_idx].in = search_counter; + std::pop_heap(pq.begin(), pq.end(), comp); + pq.pop_back(); + + Node& n = nodes[front_idx]; + if (n.g > 1e24) break; + + for (auto& [nb, edge_cost] : g.adj[front_idx]) { + double cost = n.g + edge_cost; + if (nodes[nb].in == search_counter) continue; + if (cost >= nodes[nb].g) continue; + + bool in_list = nodes[nb].in == search_counter + 1; + + nodes[nb].g = cost; + nodes[nb].t = cost; + nodes[nb].prev = front_idx; + nodes[nb].in = search_counter + 1; + + if (in_list) { + decrease_keys++; + // DEFECT: linear scan to find position in pq + auto it = std::find(pq.begin(), pq.end(), nb); + ops += (long long)(it - pq.begin()) + 1; + if (it != pq.end()) { + std::push_heap(pq.begin(), it + 1, comp); + } + } else { + pq.push_back(nb); + std::push_heap(pq.begin(), pq.end(), comp); + ops++; + } + } + } + + *out_cost = nodes[dst].g; + return ops; +} + +// FIXED: lazy deletion, no std::find +long long run_fixed(const DenseGraph& g, int src, int dst, double* out_cost) { + unsigned search_counter = SEARCH_COUNTER_BASE; + std::vector nodes(g.n); + Comp comp(nodes); + + nodes[src].g = 0; + nodes[src].t = 0; + nodes[src].in = search_counter + 1; + + std::vector pq; + pq.push_back(src); + + long long ops = 0; + + while (!pq.empty()) { + std::pop_heap(pq.begin(), pq.end(), comp); + int front_idx = pq.back(); + pq.pop_back(); + + Node& n = nodes[front_idx]; + + if (n.in == search_counter) continue; + n.in = search_counter; + + if (n.g > 1e24) break; + + for (auto& [nb, edge_cost] : g.adj[front_idx]) { + double cost = n.g + edge_cost; + if (nodes[nb].in == search_counter) continue; + if (cost >= nodes[nb].g) continue; + + nodes[nb].g = cost; + nodes[nb].t = cost; + nodes[nb].prev = front_idx; + nodes[nb].in = search_counter + 1; + + pq.push_back(nb); + std::push_heap(pq.begin(), pq.end(), comp); + ops++; + } + } + + *out_cost = nodes[dst].g; + return ops; +} + +int main() { + printf("wesnoth-0001-test: A* pathfinding std::find on pq (CWE-407)\n\n"); + + // Test 1: Correctness + { + printf("Test 1: correctness on small dense graph\n"); + DenseGraph g; + g.init(100, 10, 42); + + double cost_d, cost_f; + run_defective(g, 0, 99, &cost_d); + run_fixed(g, 0, 99, &cost_f); + + assert(std::abs(cost_d - cost_f) < 1e-9); + printf(" PASS (both find cost %.2f)\n", cost_d); + } + + // Test 2: Performance on medium graph + { + printf("\nTest 2: performance on dense graph (N=2000, E~20/node)\n"); + DenseGraph g; + g.init(2000, 20, 123); + + double cost_d, cost_f; + long long ops_defect = run_defective(g, 0, 1999, &cost_d); + long long ops_fixed = run_fixed(g, 0, 1999, &cost_f); + + assert(std::abs(cost_d - cost_f) < 1e-9); + + double ratio = (double)ops_defect / (double)ops_fixed; + + printf(" defective ops: %lld\n", ops_defect); + printf(" fixed ops: %lld\n", ops_fixed); + printf(" ratio: %.1fx\n", ratio); + printf(" cost: %.2f (both agree)\n", cost_d); + assert(ratio > 5.0); + printf(" PASS (ratio > 5x)\n"); + } + + // Test 3: Performance on larger graph + { + printf("\nTest 3: performance on dense graph (N=5000, E~20/node)\n"); + DenseGraph g; + g.init(5000, 20, 456); + + double cost_d, cost_f; + long long ops_defect = run_defective(g, 0, 4999, &cost_d); + long long ops_fixed = run_fixed(g, 0, 4999, &cost_f); + + assert(std::abs(cost_d - cost_f) < 1e-9); + + double ratio = (double)ops_defect / (double)ops_fixed; + + printf(" defective ops: %lld\n", ops_defect); + printf(" fixed ops: %lld\n", ops_fixed); + printf(" ratio: %.1fx\n", ratio); + printf(" cost: %.2f (both agree)\n", cost_d); + assert(ratio > 10.0); + printf(" PASS (ratio > 10x)\n"); + } + + printf("\nAll tests PASSED.\n"); + return 0; +} diff --git a/defects/wesnoth-0002/patch/wesnoth-0002.patch b/defects/wesnoth-0002/patch/wesnoth-0002.patch new file mode 100644 index 000000000..f3bb18e59 --- /dev/null +++ b/defects/wesnoth-0002/patch/wesnoth-0002.patch @@ -0,0 +1,63 @@ +# UNDF: UNDF-2026-000000971 +--- a/src/server/wesnothd/server.hpp ++++ b/src/server/wesnothd/server.hpp +@@ -111,6 +111,18 @@ private: + struct connection_log + { + std::string nick, ip; ++ std::chrono::system_clock::time_point log_off; ++ ++ bool operator==(const connection_log& c) const ++ { ++ // log off time does not matter to find ip-nick pairs ++ return c.nick == nick && c.ip == ip; ++ } ++ }; ++ ++ struct connection_log_hash ++ { ++ std::size_t operator()(const connection_log& c) const + { ++ std::size_t h1 = std::hash{}(c.nick); ++ std::size_t h2 = std::hash{}(c.ip); ++ return h1 ^ (h2 << 1); + } + }; + +- std::deque ip_log_; ++ // Use an unordered_set for O(1) lookup instead of linear scan on deque. ++ // Maintain a deque alongside for LRU eviction order. ++ std::deque ip_log_; ++ std::unordered_set ip_log_set_; + +--- a/src/server/wesnothd/server.cpp ++++ b/src/server/wesnothd/server.cpp +@@ -896,7 +896,7 @@ void server::handle_new_client(socket_ptr socket) + connection_log ip_name { username, client_address(socket), {} }; + +- if(std::find(ip_log_.begin(), ip_log_.end(), ip_name) == ip_log_.end()) { ++ if(ip_log_set_.find(ip_name) == ip_log_set_.end()) { + ip_log_.push_back(ip_name); ++ ip_log_set_.insert(ip_name); + + // Remove the oldest entry if the size of the IP log exceeds the maximum size + if(ip_log_.size() > max_ip_log_size_) { ++ ip_log_set_.erase(ip_log_.front()); + ip_log_.pop_front(); + } + } + +@@ -2279,7 +2279,7 @@ void server::remove_player(player_iterator iter) + connection_log ip_name { iter->info().name(), ip, {} }; + +- auto i = std::find(ip_log_.begin(), ip_log_.end(), ip_name); ++ auto i = ip_log_set_.find(ip_name); +- if(i != ip_log_.end()) { ++ if(i != ip_log_set_.end()) { +- i->log_off = std::chrono::system_clock::now(); ++ // Update log_off in the deque entry ++ auto di = std::find(ip_log_.begin(), ip_log_.end(), ip_name); ++ if(di != ip_log_.end()) { ++ di->log_off = std::chrono::system_clock::now(); ++ } + } diff --git a/defects/wesnoth-0002/test/wesnoth-0002-test b/defects/wesnoth-0002/test/wesnoth-0002-test new file mode 100755 index 0000000000000000000000000000000000000000..75893ed0136c7970394b27332857b35010453b01 GIT binary patch literal 35896 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 alongside the deque for O(1) +// membership lookup. Keep the deque for LRU eviction order. +// +// BUILD: g++ -std=c++17 -O2 -o wesnoth-0002-test wesnoth-0002-test.cpp && ./wesnoth-0002-test + +#include +#include +#include +#include +#include +#include +#include + +struct connection_log { + std::string nick, ip; + + bool operator==(const connection_log& c) const { + return c.nick == nick && c.ip == ip; + } +}; + +struct connection_log_hash { + std::size_t operator()(const connection_log& c) const { + std::size_t h1 = std::hash{}(c.nick); + std::size_t h2 = std::hash{}(c.ip); + return h1 ^ (h2 << 1); + } +}; + +// DEFECTIVE: linear scan on deque +struct DefectiveIpLog { + std::deque ip_log_; + std::size_t max_size_; + + DefectiveIpLog(std::size_t max_sz) : max_size_(max_sz) {} + + long long login(const std::string& nick, const std::string& ip) { + connection_log entry{nick, ip}; + long long ops = 0; + + // Linear scan + auto it = ip_log_.begin(); + for (; it != ip_log_.end(); ++it) { + ops++; + if (*it == entry) break; + } + + if (it == ip_log_.end()) { + ip_log_.push_back(entry); + if (ip_log_.size() > max_size_) { + ip_log_.pop_front(); + } + } + return ops; + } +}; + +// FIXED: unordered_set for O(1) lookup +struct FixedIpLog { + std::deque ip_log_; + std::unordered_set ip_log_set_; + std::size_t max_size_; + + FixedIpLog(std::size_t max_sz) : max_size_(max_sz) {} + + long long login(const std::string& nick, const std::string& ip) { + connection_log entry{nick, ip}; + long long ops = 1; // hash lookup = 1 op + + if (ip_log_set_.find(entry) == ip_log_set_.end()) { + ip_log_.push_back(entry); + ip_log_set_.insert(entry); + if (ip_log_.size() > max_size_) { + ip_log_set_.erase(ip_log_.front()); + ip_log_.pop_front(); + } + } + return ops; + } +}; + +int main() { + printf("wesnoth-0002-test: server ip_log_ deque linear scan (CWE-407)\n\n"); + + const int MAX_LOG = 500; + const int NUM_LOGINS = 2000; + + // Test 1: Correctness + { + printf("Test 1: correctness\n"); + DefectiveIpLog defective(MAX_LOG); + FixedIpLog fixed(MAX_LOG); + + for (int i = 0; i < NUM_LOGINS; i++) { + std::string nick = "user" + std::to_string(i % 600); + std::string ip = "10.0." + std::to_string((i / 256) % 256) + "." + std::to_string(i % 256); + defective.login(nick, ip); + fixed.login(nick, ip); + } + + // Both should have same entries + assert(defective.ip_log_.size() == fixed.ip_log_.size()); + for (size_t i = 0; i < defective.ip_log_.size(); i++) { + assert(defective.ip_log_[i] == fixed.ip_log_[i]); + } + printf(" PASS\n"); + } + + // Test 2: Performance + { + printf("\nTest 2: performance with %d logins, max_log=%d\n", NUM_LOGINS, MAX_LOG); + + DefectiveIpLog defective(MAX_LOG); + FixedIpLog fixed(MAX_LOG); + + long long defect_ops = 0; + long long fixed_ops = 0; + + for (int i = 0; i < NUM_LOGINS; i++) { + // Mix of new and repeat logins + std::string nick = "player" + std::to_string(i % 700); + std::string ip = "192.168." + std::to_string((i / 256) % 256) + "." + std::to_string(i % 256); + defect_ops += defective.login(nick, ip); + fixed_ops += fixed.login(nick, ip); + } + + double ratio = (double)defect_ops / (double)fixed_ops; + + printf(" defective ops: %lld\n", defect_ops); + printf(" fixed ops: %lld\n", fixed_ops); + printf(" ratio: %.1fx\n", ratio); + assert(ratio > 50.0); + printf(" PASS (ratio > 50x)\n"); + } + + // Test 3: Worst case (all unique logins filling the log) + { + printf("\nTest 3: worst case, all unique logins\n"); + + DefectiveIpLog defective(MAX_LOG); + FixedIpLog fixed(MAX_LOG); + + long long defect_ops = 0; + long long fixed_ops = 0; + + for (int i = 0; i < NUM_LOGINS; i++) { + std::string nick = "unique_user_" + std::to_string(i); + std::string ip = "10." + std::to_string((i / 65536) % 256) + "." + std::to_string((i / 256) % 256) + "." + std::to_string(i % 256); + defect_ops += defective.login(nick, ip); + fixed_ops += fixed.login(nick, ip); + } + + double ratio = (double)defect_ops / (double)fixed_ops; + + printf(" defective ops: %lld\n", defect_ops); + printf(" fixed ops: %lld\n", fixed_ops); + printf(" ratio: %.1fx\n", ratio); + assert(ratio > 100.0); + printf(" PASS (ratio > 100x)\n"); + } + + printf("\nAll tests PASSED.\n"); + return 0; +} diff --git a/defects/wesnoth-0003/patch/wesnoth-0003.patch b/defects/wesnoth-0003/patch/wesnoth-0003.patch new file mode 100644 index 000000000..54097c4f9 --- /dev/null +++ b/defects/wesnoth-0003/patch/wesnoth-0003.patch @@ -0,0 +1,67 @@ +# UNDF: UNDF-2026-000000972 +--- a/src/units/types.cpp ++++ b/src/units/types.cpp +@@ -458,10 +458,11 @@ std::vector unit_type::special_notes() const { + + static void append_special_note(std::vector& notes, const t_string& new_note) { + if(new_note.empty()) return; + std::string_view note_plain = new_note.c_str(); + utils::trim(note_plain); + if(note_plain.empty()) return; +- if(utils::contains(notes, new_note)) return; + notes.push_back(new_note); + } + +-std::vector combine_special_notes(const std::vector& direct, const config& abilities, const const_attack_itors& attacks, const movetype& mt) ++std::vector combine_special_notes(const std::vector& direct, const config& abilities, const const_attack_itors& attacks, const movetype& mt) + { +- std::vector notes; ++ std::vector notes; ++ // Use a set to track seen notes for O(1) dedup instead of ++ // O(N) linear scan via utils::contains on vector per insertion. ++ std::set seen; + for(const auto& note : direct) { +- append_special_note(notes, note); ++ std::string key(note.c_str()); ++ if(!key.empty() && seen.insert(key).second) { ++ notes.push_back(note); ++ } + } + for(const auto [key, cfg] : abilities.all_children_view()) { + if(cfg.has_attribute("special_note")) { +- append_special_note(notes, cfg["special_note"].t_str()); ++ const t_string& sn = cfg["special_note"].t_str(); ++ std::string k(sn.c_str()); ++ if(!k.empty() && seen.insert(k).second) { ++ notes.push_back(sn); ++ } + } + } + for(const auto& attack : attacks) { + for(const auto& p_ab : attack.specials()) { + if(p_ab->cfg().has_attribute("special_note")) { +- append_special_note(notes, p_ab->cfg()["special_note"].t_str()); ++ const t_string& sn = p_ab->cfg()["special_note"].t_str(); ++ std::string k(sn.c_str()); ++ if(!k.empty() && seen.insert(k).second) { ++ notes.push_back(sn); ++ } + } + } + if(auto attack_type_note = string_table.find("special_note_damage_type_" + attack.type()); attack_type_note != string_table.end()) { +- append_special_note(notes, attack_type_note->second); ++ std::string k(attack_type_note->second.c_str()); ++ if(!k.empty() && seen.insert(k).second) { ++ notes.push_back(attack_type_note->second); ++ } + } + } + for(const auto& move_note : mt.special_notes()) { +- append_special_note(notes, move_note); ++ std::string k(move_note.c_str()); ++ if(!k.empty() && seen.insert(k).second) { ++ notes.push_back(move_note); ++ } + } + return notes; + } diff --git a/defects/wesnoth-0003/test/wesnoth-0003-test b/defects/wesnoth-0003/test/wesnoth-0003-test new file mode 100755 index 0000000000000000000000000000000000000000..66cf48bcd210163d8999bc4c1b92d2ebea237c81 GIT binary patch literal 27432 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 Waa!)Y@0>>P%N7lro+Xfl3jZ56^x2vK literal 0 HcmV?d00001 diff --git a/defects/wesnoth-0003/test/wesnoth-0003-test.cpp b/defects/wesnoth-0003/test/wesnoth-0003-test.cpp new file mode 100644 index 000000000..d710ee1bd --- /dev/null +++ b/defects/wesnoth-0003/test/wesnoth-0003-test.cpp @@ -0,0 +1,141 @@ +// wesnoth-0003-test.cpp +// Unit test: combine_special_notes O(N^2) dedup (CWE-407) +// +// DEFECT: In types.cpp, combine_special_notes calls append_special_note for +// every note from direct notes, abilities, attack specials, damage types, +// and movement type. Each call does utils::contains(notes, new_note) which +// is std::find on a vector, making the total cost O(N^2) where N is the +// total number of note insertions. +// +// FIX: Use a std::set to track seen notes for O(log N) dedup +// (or unordered_set for O(1)). This reduces total cost to O(N log N). +// +// BUILD: g++ -std=c++17 -O2 -o wesnoth-0003-test wesnoth-0003-test.cpp && ./wesnoth-0003-test + +#include +#include +#include +#include +#include +#include + +// Simulate the defect: O(N) contains check per insertion +long long defective_combine(const std::vector& input) { + std::vector notes; + long long ops = 0; + + for (const auto& note : input) { + if (note.empty()) continue; + // Linear scan for dedup + bool found = false; + for (const auto& existing : notes) { + ops++; + if (existing == note) { found = true; break; } + } + if (!found) { + notes.push_back(note); + } + } + return ops; +} + +// Fixed: set-based dedup +long long fixed_combine(const std::vector& input) { + std::vector notes; + std::set seen; + long long ops = 0; + + for (const auto& note : input) { + if (note.empty()) continue; + ops++; // set insertion/lookup + if (seen.insert(note).second) { + notes.push_back(note); + } + } + return ops; +} + +int main() { + printf("wesnoth-0003-test: combine_special_notes O(N^2) dedup (CWE-407)\n\n"); + + // Test 1: Correctness + { + printf("Test 1: correctness\n"); + std::vector input = { + "Poison attack", "First strike", "Poison attack", + "Regenerates", "First strike", "Skirmisher", + "Regenerates", "Marksman", "" + }; + + // Defective approach + std::vector defect_result; + for (const auto& note : input) { + if (note.empty()) continue; + bool found = false; + for (const auto& e : defect_result) { + if (e == note) { found = true; break; } + } + if (!found) defect_result.push_back(note); + } + + // Fixed approach + std::vector fixed_result; + std::set seen; + for (const auto& note : input) { + if (note.empty()) continue; + if (seen.insert(note).second) { + fixed_result.push_back(note); + } + } + + assert(defect_result == fixed_result); + printf(" PASS (same output: %zu unique notes)\n", defect_result.size()); + } + + // Test 2: Performance with many notes (simulating unit with many abilities) + { + const int N = 500; + printf("\nTest 2: performance with N=%d notes (50%% duplicates)\n", N); + + std::vector input; + for (int i = 0; i < N; i++) { + input.push_back("special_note_" + std::to_string(i % (N / 2))); + } + + long long defect_ops = defective_combine(input); + long long fixed_ops = fixed_combine(input); + + double ratio = (double)defect_ops / (double)fixed_ops; + + printf(" defective ops: %lld\n", defect_ops); + printf(" fixed ops: %lld\n", fixed_ops); + printf(" ratio: %.1fx\n", ratio); + assert(ratio > 50.0); + printf(" PASS (ratio > 50x)\n"); + } + + // Test 3: All unique notes (worst case for defective) + { + const int N = 1000; + printf("\nTest 3: worst case, N=%d all unique notes\n", N); + + std::vector input; + for (int i = 0; i < N; i++) { + input.push_back("unique_note_" + std::to_string(i)); + } + + long long defect_ops = defective_combine(input); + long long fixed_ops = fixed_combine(input); + + double ratio = (double)defect_ops / (double)fixed_ops; + + printf(" defective ops: %lld\n", defect_ops); + printf(" fixed ops: %lld\n", fixed_ops); + printf(" ratio: %.1fx\n", ratio); + assert(ratio > 100.0); + printf(" PASS (ratio > 100x)\n"); + } + + printf("\nAll tests PASSED.\n"); + return 0; +}