@@ -63,15 +63,101 @@ fn serialize_daac<S: Serializer>(
6363 s. serialize_bytes ( & daac. serialize ( ) )
6464}
6565
66+ fn read_u32_le ( bytes : & [ u8 ] , offset : usize ) -> u32 {
67+ u32:: from_le_bytes (
68+ bytes[ offset..offset + 4 ]
69+ . try_into ( )
70+ . expect ( "slice of length 4" ) ,
71+ )
72+ }
73+
74+ fn validate_daac_bytes ( v : & [ u8 ] ) -> Result < ( ) , & ' static str > {
75+ const STATE_SIZE : usize = 12 ;
76+ const OUTPUT_SIZE : usize = 12 ;
77+ const MAX_OUTPUTS : u32 = 0x00ff_ffff ;
78+
79+ if v. len ( ) < 4 {
80+ return Err ( "truncated DoubleArrayAhoCorasick states length" ) ;
81+ }
82+ let states_len = read_u32_le ( v, 0 ) ;
83+ if states_len < 256 || !states_len. is_multiple_of ( 256 ) {
84+ return Err ( "invalid DoubleArrayAhoCorasick states length" ) ;
85+ }
86+ let states_bytes_len = ( states_len as usize )
87+ . checked_mul ( STATE_SIZE )
88+ . ok_or ( "DoubleArrayAhoCorasick states byte length overflow" ) ?;
89+ let outputs_offset = 4usize
90+ . checked_add ( states_bytes_len)
91+ . ok_or ( "DoubleArrayAhoCorasick states offset overflow" ) ?;
92+ let outputs_header_end = outputs_offset
93+ . checked_add ( 4 )
94+ . ok_or ( "DoubleArrayAhoCorasick outputs header overflow" ) ?;
95+ if v. len ( ) < outputs_header_end {
96+ return Err ( "truncated DoubleArrayAhoCorasick states data" ) ;
97+ }
98+
99+ let outputs_len = read_u32_le ( v, outputs_offset) ;
100+ if outputs_len > MAX_OUTPUTS {
101+ return Err ( "invalid DoubleArrayAhoCorasick outputs length" ) ;
102+ }
103+ let outputs_bytes_len = ( outputs_len as usize )
104+ . checked_mul ( OUTPUT_SIZE )
105+ . ok_or ( "DoubleArrayAhoCorasick outputs byte length overflow" ) ?;
106+ let outputs_end = outputs_header_end
107+ . checked_add ( outputs_bytes_len)
108+ . ok_or ( "DoubleArrayAhoCorasick outputs end overflow" ) ?;
109+ let total_len = outputs_end
110+ . checked_add ( 5 )
111+ . ok_or ( "DoubleArrayAhoCorasick total length overflow" ) ?;
112+ if v. len ( ) != total_len {
113+ return Err ( "unexpected DoubleArrayAhoCorasick byte length" ) ;
114+ }
115+
116+ let match_kind = v[ outputs_end] ;
117+ if match_kind > 2 {
118+ return Err ( "invalid DoubleArrayAhoCorasick match kind" ) ;
119+ }
120+ let num_states = read_u32_le ( v, outputs_end + 1 ) ;
121+ if num_states > states_len {
122+ return Err ( "invalid DoubleArrayAhoCorasick num_states" ) ;
123+ }
124+
125+ for chunk in v[ 4 ..outputs_offset] . as_chunks :: < STATE_SIZE > ( ) . 0 {
126+ let base = read_u32_le ( chunk, 0 ) ;
127+ let fail = read_u32_le ( chunk, 4 ) ;
128+ let opos_ch = read_u32_le ( chunk, 8 ) ;
129+ let output_pos = opos_ch >> 8 ;
130+ if base >= states_len || fail >= states_len || output_pos > outputs_len {
131+ return Err ( "DoubleArrayAhoCorasick state out of bounds" ) ;
132+ }
133+ }
134+
135+ for ( i, chunk) in v[ outputs_header_end..outputs_end]
136+ . as_chunks :: < OUTPUT_SIZE > ( )
137+ . 0
138+ . iter ( )
139+ . enumerate ( )
140+ {
141+ let length = read_u32_le ( chunk, 4 ) ;
142+ let parent = read_u32_le ( chunk, 8 ) ;
143+ if length == 0 || ( parent as usize ) > i {
144+ return Err ( "DoubleArrayAhoCorasick output out of bounds" ) ;
145+ }
146+ }
147+
148+ Ok ( ( ) )
149+ }
150+
66151struct DaacVisitor ;
67152impl Visitor < ' _ > for DaacVisitor {
68153 type Value = DoubleArrayAhoCorasick < u32 > ;
69154
70- fn expecting ( & self , _formatter : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
71- Err ( std :: fmt :: Error )
155+ fn expecting ( & self , formatter : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
156+ formatter . write_str ( "a serialized DoubleArrayAhoCorasick automaton" )
72157 }
73158
74159 fn visit_bytes < E : serde:: de:: Error > ( self , v : & [ u8 ] ) -> Result < Self :: Value , E > {
160+ validate_daac_bytes ( v) . map_err ( E :: custom) ?;
75161 Ok ( unsafe { DoubleArrayAhoCorasick :: deserialize_unchecked ( v) . 0 } )
76162 }
77163}
@@ -114,20 +200,20 @@ fn is_valid_token_pair(
114200 // Reverse the merge operation from BPE.
115201 if token1 > token2 {
116202 limit = token1;
117- token1 = unsafe { split_table. get_unchecked ( token1 as usize ) . 1 } ;
203+ token1 = split_table[ token1 as usize ] . 1 ;
118204 if token1 == limit {
119205 limit = token2 + 1 ;
120- token2 = unsafe { split_table. get_unchecked ( token2 as usize ) . 0 } ;
206+ token2 = split_table[ token2 as usize ] . 0 ;
121207 if token2 + 1 == limit {
122208 return true ;
123209 }
124210 }
125211 } else {
126212 limit = token2 + 1 ;
127- token2 = unsafe { split_table. get_unchecked ( token2 as usize ) . 0 } ;
213+ token2 = split_table[ token2 as usize ] . 0 ;
128214 if token2 + 1 == limit {
129215 limit = token1;
130- token1 = unsafe { split_table. get_unchecked ( token1 as usize ) . 1 } ;
216+ token1 = split_table[ token1 as usize ] . 1 ;
131217 if token1 == limit {
132218 return true ;
133219 }
@@ -137,10 +223,8 @@ fn is_valid_token_pair(
137223}
138224
139225fn token_range ( token_starts : & [ u32 ] , token_id : u32 ) -> Range < usize > {
140- unsafe {
141- * token_starts. get_unchecked ( token_id as usize ) as usize
142- ..* token_starts. get_unchecked ( token_id as usize + 1 ) as usize
143- }
226+ let idx = token_id as usize ;
227+ token_starts[ idx] as usize ..token_starts[ idx + 1 ] as usize
144228}
145229
146230fn token_bytes < ' a > ( all_tokens : & ' a [ u8 ] , token_starts : & [ u32 ] , token_id : u32 ) -> & ' a [ u8 ] {
@@ -404,7 +488,7 @@ impl BytePairEncoding {
404488 last_token. push ( new_token) ;
405489 break ;
406490 } else {
407- let prev_token = unsafe { * last_token. get_unchecked ( new_range. start - 1 ) } ;
491+ let prev_token = last_token[ new_range. start - 1 ] ;
408492 if self . is_valid_token_pair ( prev_token, new_token) {
409493 last_token. push ( new_token) ;
410494 break ;
@@ -694,7 +778,9 @@ pub fn create_test_bytes(bpe: &BytePairEncoding, min_bytes: usize) -> Vec<u8> {
694778
695779#[ cfg( test) ]
696780mod tests {
697- use super :: hash_bytes;
781+ use aneubeck_daachorse:: DoubleArrayAhoCorasick ;
782+
783+ use super :: { hash_bytes, read_u32_le, validate_daac_bytes, BytePairEncoding } ;
698784
699785 /// The hash must not depend on the target. It keys the serialized `bytes_hash_to_token`,
700786 /// and a hardcoded hash factor is only collision-free for one particular mapping.
@@ -712,4 +798,46 @@ mod tests {
712798 // Tiktoken vocabularies contain arbitrary bytes, not just UTF-8.
713799 assert_eq ! ( hash_bytes( & [ 0 , 0xff , 0x80 ] , FACTOR ) , 3276605559 ) ;
714800 }
801+
802+ #[ test]
803+ #[ should_panic]
804+ fn token_bytes_out_of_bounds_panics ( ) {
805+ let tokens: Vec < Vec < u8 > > = ( 0 ..=255u8 ) . map ( |b| vec ! [ b] ) . collect ( ) ;
806+ let bpe = BytePairEncoding :: from_dictionary ( tokens, None ) ;
807+ let _ = bpe. token_bytes ( 999 ) ;
808+ }
809+
810+ #[ test]
811+ fn validate_daac_bytes_checks_invariants ( ) {
812+ let pma =
813+ DoubleArrayAhoCorasick :: < u32 > :: new ( vec ! [ "a" , "ab" , "bab" ] ) . expect ( "valid patterns" ) ;
814+ let valid = pma. serialize ( ) ;
815+ assert ! ( validate_daac_bytes( & valid) . is_ok( ) ) ;
816+
817+ // Truncated input
818+ assert ! ( validate_daac_bytes( & valid[ ..valid. len( ) - 1 ] ) . is_err( ) ) ;
819+
820+ // Non-multiple-of-256 states_len
821+ let mut bad_states_len = valid. clone ( ) ;
822+ bad_states_len[ 0 ..4 ] . copy_from_slice ( & 255u32 . to_le_bytes ( ) ) ;
823+ assert ! ( validate_daac_bytes( & bad_states_len) . is_err( ) ) ;
824+
825+ // Out-of-bounds state `base`
826+ let mut bad_base = valid. clone ( ) ;
827+ bad_base[ 4 ..8 ] . copy_from_slice ( & 9999u32 . to_le_bytes ( ) ) ;
828+ assert ! ( validate_daac_bytes( & bad_base) . is_err( ) ) ;
829+
830+ // Out-of-bounds state `fail`
831+ let mut bad_fail = valid. clone ( ) ;
832+ bad_fail[ 8 ..12 ] . copy_from_slice ( & 9999u32 . to_le_bytes ( ) ) ;
833+ assert ! ( validate_daac_bytes( & bad_fail) . is_err( ) ) ;
834+
835+ // Out-of-bounds output `parent` (self-referential 1-based parent == 1 at index 0)
836+ let states_len = read_u32_le ( & valid, 0 ) as usize ;
837+ let first_output_parent_offset = 4 + states_len * 12 + 4 + 8 ;
838+ let mut bad_parent = valid. clone ( ) ;
839+ bad_parent[ first_output_parent_offset..first_output_parent_offset + 4 ]
840+ . copy_from_slice ( & 1u32 . to_le_bytes ( ) ) ;
841+ assert ! ( validate_daac_bytes( & bad_parent) . is_err( ) ) ;
842+ }
715843}
0 commit comments