123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- package gofuzzheaders
- import (
- "fmt"
- "strings"
- )
- // returns a keyword by index
- func getKeyword(f *ConsumeFuzzer) (string, error) {
- index, err := f.GetInt()
- if err != nil {
- return keywords[0], err
- }
- for i, k := range keywords {
- if i == index {
- return k, nil
- }
- }
- return keywords[0], fmt.Errorf("could not get a kw")
- }
- // Simple utility function to check if a string
- // slice contains a string.
- func containsString(s []string, e string) bool {
- for _, a := range s {
- if a == e {
- return true
- }
- }
- return false
- }
- // These keywords are used specifically for fuzzing Vitess
- var keywords = []string{
- "accessible", "action", "add", "after", "against", "algorithm",
- "all", "alter", "always", "analyze", "and", "as", "asc", "asensitive",
- "auto_increment", "avg_row_length", "before", "begin", "between",
- "bigint", "binary", "_binary", "_utf8mb4", "_utf8", "_latin1", "bit",
- "blob", "bool", "boolean", "both", "by", "call", "cancel", "cascade",
- "cascaded", "case", "cast", "channel", "change", "char", "character",
- "charset", "check", "checksum", "coalesce", "code", "collate", "collation",
- "column", "columns", "comment", "committed", "commit", "compact", "complete",
- "compressed", "compression", "condition", "connection", "constraint", "continue",
- "convert", "copy", "cume_dist", "substr", "substring", "create", "cross",
- "csv", "current_date", "current_time", "current_timestamp", "current_user",
- "cursor", "data", "database", "databases", "day", "day_hour", "day_microsecond",
- "day_minute", "day_second", "date", "datetime", "dec", "decimal", "declare",
- "default", "definer", "delay_key_write", "delayed", "delete", "dense_rank",
- "desc", "describe", "deterministic", "directory", "disable", "discard",
- "disk", "distinct", "distinctrow", "div", "double", "do", "drop", "dumpfile",
- "duplicate", "dynamic", "each", "else", "elseif", "empty", "enable",
- "enclosed", "encryption", "end", "enforced", "engine", "engines", "enum",
- "error", "escape", "escaped", "event", "exchange", "exclusive", "exists",
- "exit", "explain", "expansion", "export", "extended", "extract", "false",
- "fetch", "fields", "first", "first_value", "fixed", "float", "float4",
- "float8", "flush", "for", "force", "foreign", "format", "from", "full",
- "fulltext", "function", "general", "generated", "geometry", "geometrycollection",
- "get", "global", "gtid_executed", "grant", "group", "grouping", "groups",
- "group_concat", "having", "header", "high_priority", "hosts", "hour", "hour_microsecond",
- "hour_minute", "hour_second", "if", "ignore", "import", "in", "index", "indexes",
- "infile", "inout", "inner", "inplace", "insensitive", "insert", "insert_method",
- "int", "int1", "int2", "int3", "int4", "int8", "integer", "interval",
- "into", "io_after_gtids", "is", "isolation", "iterate", "invoker", "join",
- "json", "json_table", "key", "keys", "keyspaces", "key_block_size", "kill", "lag",
- "language", "last", "last_value", "last_insert_id", "lateral", "lead", "leading",
- "leave", "left", "less", "level", "like", "limit", "linear", "lines",
- "linestring", "load", "local", "localtime", "localtimestamp", "lock", "logs",
- "long", "longblob", "longtext", "loop", "low_priority", "manifest",
- "master_bind", "match", "max_rows", "maxvalue", "mediumblob", "mediumint",
- "mediumtext", "memory", "merge", "microsecond", "middleint", "min_rows", "minute",
- "minute_microsecond", "minute_second", "mod", "mode", "modify", "modifies",
- "multilinestring", "multipoint", "multipolygon", "month", "name",
- "names", "natural", "nchar", "next", "no", "none", "not", "no_write_to_binlog",
- "nth_value", "ntile", "null", "numeric", "of", "off", "offset", "on",
- "only", "open", "optimize", "optimizer_costs", "option", "optionally",
- "or", "order", "out", "outer", "outfile", "over", "overwrite", "pack_keys",
- "parser", "partition", "partitioning", "password", "percent_rank", "plugins",
- "point", "polygon", "precision", "primary", "privileges", "processlist",
- "procedure", "query", "quarter", "range", "rank", "read", "reads", "read_write",
- "real", "rebuild", "recursive", "redundant", "references", "regexp", "relay",
- "release", "remove", "rename", "reorganize", "repair", "repeat", "repeatable",
- "replace", "require", "resignal", "restrict", "return", "retry", "revert",
- "revoke", "right", "rlike", "rollback", "row", "row_format", "row_number",
- "rows", "s3", "savepoint", "schema", "schemas", "second", "second_microsecond",
- "security", "select", "sensitive", "separator", "sequence", "serializable",
- "session", "set", "share", "shared", "show", "signal", "signed", "slow",
- "smallint", "spatial", "specific", "sql", "sqlexception", "sqlstate",
- "sqlwarning", "sql_big_result", "sql_cache", "sql_calc_found_rows",
- "sql_no_cache", "sql_small_result", "ssl", "start", "starting",
- "stats_auto_recalc", "stats_persistent", "stats_sample_pages", "status",
- "storage", "stored", "straight_join", "stream", "system", "vstream",
- "table", "tables", "tablespace", "temporary", "temptable", "terminated",
- "text", "than", "then", "time", "timestamp", "timestampadd", "timestampdiff",
- "tinyblob", "tinyint", "tinytext", "to", "trailing", "transaction", "tree",
- "traditional", "trigger", "triggers", "true", "truncate", "uncommitted",
- "undefined", "undo", "union", "unique", "unlock", "unsigned", "update",
- "upgrade", "usage", "use", "user", "user_resources", "using", "utc_date",
- "utc_time", "utc_timestamp", "validation", "values", "variables", "varbinary",
- "varchar", "varcharacter", "varying", "vgtid_executed", "virtual", "vindex",
- "vindexes", "view", "vitess", "vitess_keyspaces", "vitess_metadata",
- "vitess_migration", "vitess_migrations", "vitess_replication_status",
- "vitess_shards", "vitess_tablets", "vschema", "warnings", "when",
- "where", "while", "window", "with", "without", "work", "write", "xor",
- "year", "year_month", "zerofill",
- }
- // Keywords that could get an additional keyword
- var needCustomString = []string{
- "DISTINCTROW", "FROM", // Select keywords:
- "GROUP BY", "HAVING", "WINDOW",
- "FOR",
- "ORDER BY", "LIMIT",
- "INTO", "PARTITION", "AS", // Insert Keywords:
- "ON DUPLICATE KEY UPDATE",
- "WHERE", "LIMIT", // Delete keywords
- "INFILE", "INTO TABLE", "CHARACTER SET", // Load keywords
- "TERMINATED BY", "ENCLOSED BY",
- "ESCAPED BY", "STARTING BY",
- "TERMINATED BY", "STARTING BY",
- "IGNORE",
- "VALUE", "VALUES", // Replace tokens
- "SET", // Update tokens
- "ENGINE =", // Drop tokens
- "DEFINER =", "ON SCHEDULE", "RENAME TO", // Alter tokens
- "COMMENT", "DO", "INITIAL_SIZE = ", "OPTIONS",
- }
- var alterTableTokens = [][]string{
- {"CUSTOM_FUZZ_STRING"},
- {"CUSTOM_ALTTER_TABLE_OPTIONS"},
- {"PARTITION_OPTIONS_FOR_ALTER_TABLE"},
- }
- var alterTokens = [][]string{
- {
- "DATABASE", "SCHEMA", "DEFINER = ", "EVENT", "FUNCTION", "INSTANCE",
- "LOGFILE GROUP", "PROCEDURE", "SERVER",
- },
- {"CUSTOM_FUZZ_STRING"},
- {
- "ON SCHEDULE", "ON COMPLETION PRESERVE", "ON COMPLETION NOT PRESERVE",
- "ADD UNDOFILE", "OPTIONS",
- },
- {"RENAME TO", "INITIAL_SIZE = "},
- {"ENABLE", "DISABLE", "DISABLE ON SLAVE", "ENGINE"},
- {"COMMENT"},
- {"DO"},
- }
- var setTokens = [][]string{
- {"CHARACTER SET", "CHARSET", "CUSTOM_FUZZ_STRING", "NAMES"},
- {"CUSTOM_FUZZ_STRING", "DEFAULT", "="},
- {"CUSTOM_FUZZ_STRING"},
- }
- var dropTokens = [][]string{
- {"TEMPORARY", "UNDO"},
- {
- "DATABASE", "SCHEMA", "EVENT", "INDEX", "LOGFILE GROUP",
- "PROCEDURE", "FUNCTION", "SERVER", "SPATIAL REFERENCE SYSTEM",
- "TABLE", "TABLESPACE", "TRIGGER", "VIEW",
- },
- {"IF EXISTS"},
- {"CUSTOM_FUZZ_STRING"},
- {"ON", "ENGINE = ", "RESTRICT", "CASCADE"},
- }
- var renameTokens = [][]string{
- {"TABLE"},
- {"CUSTOM_FUZZ_STRING"},
- {"TO"},
- {"CUSTOM_FUZZ_STRING"},
- }
- var truncateTokens = [][]string{
- {"TABLE"},
- {"CUSTOM_FUZZ_STRING"},
- }
- var createTokens = [][]string{
- {"OR REPLACE", "TEMPORARY", "UNDO"}, // For create spatial reference system
- {
- "UNIQUE", "FULLTEXT", "SPATIAL", "ALGORITHM = UNDEFINED", "ALGORITHM = MERGE",
- "ALGORITHM = TEMPTABLE",
- },
- {
- "DATABASE", "SCHEMA", "EVENT", "FUNCTION", "INDEX", "LOGFILE GROUP",
- "PROCEDURE", "SERVER", "SPATIAL REFERENCE SYSTEM", "TABLE", "TABLESPACE",
- "TRIGGER", "VIEW",
- },
- {"IF NOT EXISTS"},
- {"CUSTOM_FUZZ_STRING"},
- }
- /*
- // For future use.
- var updateTokens = [][]string{
- {"LOW_PRIORITY"},
- {"IGNORE"},
- {"SET"},
- {"WHERE"},
- {"ORDER BY"},
- {"LIMIT"},
- }
- */
- var replaceTokens = [][]string{
- {"LOW_PRIORITY", "DELAYED"},
- {"INTO"},
- {"PARTITION"},
- {"CUSTOM_FUZZ_STRING"},
- {"VALUES", "VALUE"},
- }
- var loadTokens = [][]string{
- {"DATA"},
- {"LOW_PRIORITY", "CONCURRENT", "LOCAL"},
- {"INFILE"},
- {"REPLACE", "IGNORE"},
- {"INTO TABLE"},
- {"PARTITION"},
- {"CHARACTER SET"},
- {"FIELDS", "COLUMNS"},
- {"TERMINATED BY"},
- {"OPTIONALLY"},
- {"ENCLOSED BY"},
- {"ESCAPED BY"},
- {"LINES"},
- {"STARTING BY"},
- {"TERMINATED BY"},
- {"IGNORE"},
- {"LINES", "ROWS"},
- {"CUSTOM_FUZZ_STRING"},
- }
- // These Are everything that comes after "INSERT"
- var insertTokens = [][]string{
- {"LOW_PRIORITY", "DELAYED", "HIGH_PRIORITY", "IGNORE"},
- {"INTO"},
- {"PARTITION"},
- {"CUSTOM_FUZZ_STRING"},
- {"AS"},
- {"ON DUPLICATE KEY UPDATE"},
- }
- // These are everything that comes after "SELECT"
- var selectTokens = [][]string{
- {"*", "CUSTOM_FUZZ_STRING", "DISTINCTROW"},
- {"HIGH_PRIORITY"},
- {"STRAIGHT_JOIN"},
- {"SQL_SMALL_RESULT", "SQL_BIG_RESULT", "SQL_BUFFER_RESULT"},
- {"SQL_NO_CACHE", "SQL_CALC_FOUND_ROWS"},
- {"CUSTOM_FUZZ_STRING"},
- {"FROM"},
- {"WHERE"},
- {"GROUP BY"},
- {"HAVING"},
- {"WINDOW"},
- {"ORDER BY"},
- {"LIMIT"},
- {"CUSTOM_FUZZ_STRING"},
- {"FOR"},
- }
- // These are everything that comes after "DELETE"
- var deleteTokens = [][]string{
- {"LOW_PRIORITY", "QUICK", "IGNORE", "FROM", "AS"},
- {"PARTITION"},
- {"WHERE"},
- {"ORDER BY"},
- {"LIMIT"},
- }
- var alter_table_options = []string{
- "ADD", "COLUMN", "FIRST", "AFTER", "INDEX", "KEY", "FULLTEXT", "SPATIAL",
- "CONSTRAINT", "UNIQUE", "FOREIGN KEY", "CHECK", "ENFORCED", "DROP", "ALTER",
- "NOT", "INPLACE", "COPY", "SET", "VISIBLE", "INVISIBLE", "DEFAULT", "CHANGE",
- "CHARACTER SET", "COLLATE", "DISABLE", "ENABLE", "KEYS", "TABLESPACE", "LOCK",
- "FORCE", "MODIFY", "SHARED", "EXCLUSIVE", "NONE", "ORDER BY", "RENAME COLUMN",
- "AS", "=", "ASC", "DESC", "WITH", "WITHOUT", "VALIDATION", "ADD PARTITION",
- "DROP PARTITION", "DISCARD PARTITION", "IMPORT PARTITION", "TRUNCATE PARTITION",
- "COALESCE PARTITION", "REORGANIZE PARTITION", "EXCHANGE PARTITION",
- "ANALYZE PARTITION", "CHECK PARTITION", "OPTIMIZE PARTITION", "REBUILD PARTITION",
- "REPAIR PARTITION", "REMOVE PARTITIONING", "USING", "BTREE", "HASH", "COMMENT",
- "KEY_BLOCK_SIZE", "WITH PARSER", "AUTOEXTEND_SIZE", "AUTO_INCREMENT", "AVG_ROW_LENGTH",
- "CHECKSUM", "INSERT_METHOD", "ROW_FORMAT", "DYNAMIC", "FIXED", "COMPRESSED", "REDUNDANT",
- "COMPACT", "SECONDARY_ENGINE_ATTRIBUTE", "STATS_AUTO_RECALC", "STATS_PERSISTENT",
- "STATS_SAMPLE_PAGES", "ZLIB", "LZ4", "ENGINE_ATTRIBUTE", "KEY_BLOCK_SIZE", "MAX_ROWS",
- "MIN_ROWS", "PACK_KEYS", "PASSWORD", "COMPRESSION", "CONNECTION", "DIRECTORY",
- "DELAY_KEY_WRITE", "ENCRYPTION", "STORAGE", "DISK", "MEMORY", "UNION",
- }
- // Creates an 'alter table' statement. 'alter table' is an exception
- // in that it has its own function. The majority of statements
- // are created by 'createStmt()'.
- func createAlterTableStmt(f *ConsumeFuzzer) (string, error) {
- maxArgs, err := f.GetInt()
- if err != nil {
- return "", err
- }
- maxArgs = maxArgs % 30
- if maxArgs == 0 {
- return "", fmt.Errorf("could not create alter table stmt")
- }
- var stmt strings.Builder
- stmt.WriteString("ALTER TABLE ")
- for i := 0; i < maxArgs; i++ {
- // Calculate if we get existing token or custom string
- tokenType, err := f.GetInt()
- if err != nil {
- return "", err
- }
- if tokenType%4 == 1 {
- customString, err := f.GetString()
- if err != nil {
- return "", err
- }
- stmt.WriteString(" " + customString)
- } else {
- tokenIndex, err := f.GetInt()
- if err != nil {
- return "", err
- }
- stmt.WriteString(" " + alter_table_options[tokenIndex%len(alter_table_options)])
- }
- }
- return stmt.String(), nil
- }
- func chooseToken(tokens []string, f *ConsumeFuzzer) (string, error) {
- index, err := f.GetInt()
- if err != nil {
- return "", err
- }
- var token strings.Builder
- token.WriteString(tokens[index%len(tokens)])
- if token.String() == "CUSTOM_FUZZ_STRING" {
- customFuzzString, err := f.GetString()
- if err != nil {
- return "", err
- }
- return customFuzzString, nil
- }
- // Check if token requires an argument
- if containsString(needCustomString, token.String()) {
- customFuzzString, err := f.GetString()
- if err != nil {
- return "", err
- }
- token.WriteString(" " + customFuzzString)
- }
- return token.String(), nil
- }
- var stmtTypes = map[string][][]string{
- "DELETE": deleteTokens,
- "INSERT": insertTokens,
- "SELECT": selectTokens,
- "LOAD": loadTokens,
- "REPLACE": replaceTokens,
- "CREATE": createTokens,
- "DROP": dropTokens,
- "RENAME": renameTokens,
- "TRUNCATE": truncateTokens,
- "SET": setTokens,
- "ALTER": alterTokens,
- "ALTER TABLE": alterTableTokens, // ALTER TABLE has its own set of tokens
- }
- var stmtTypeEnum = map[int]string{
- 0: "DELETE",
- 1: "INSERT",
- 2: "SELECT",
- 3: "LOAD",
- 4: "REPLACE",
- 5: "CREATE",
- 6: "DROP",
- 7: "RENAME",
- 8: "TRUNCATE",
- 9: "SET",
- 10: "ALTER",
- 11: "ALTER TABLE",
- }
- func createStmt(f *ConsumeFuzzer) (string, error) {
- stmtIndex, err := f.GetInt()
- if err != nil {
- return "", err
- }
- stmtIndex = stmtIndex % len(stmtTypes)
- queryType := stmtTypeEnum[stmtIndex]
- tokens := stmtTypes[queryType]
- // We have custom creator for ALTER TABLE
- if queryType == "ALTER TABLE" {
- query, err := createAlterTableStmt(f)
- if err != nil {
- return "", err
- }
- return query, nil
- }
- // Here we are creating a query that is not
- // an 'alter table' query. For available
- // queries, see "stmtTypes"
- // First specify the first query keyword:
- var query strings.Builder
- query.WriteString(queryType)
- // Next create the args for the
- queryArgs, err := createStmtArgs(tokens, f)
- if err != nil {
- return "", err
- }
- query.WriteString(" " + queryArgs)
- return query.String(), nil
- }
- // Creates the arguments of a statements. In a select statement
- // that would be everything after "select".
- func createStmtArgs(tokenslice [][]string, f *ConsumeFuzzer) (string, error) {
- var query, token strings.Builder
- // We go through the tokens in the tokenslice,
- // create the respective token and add it to
- // "query"
- for _, tokens := range tokenslice {
- // For extra randomization, the fuzzer can
- // choose to not include this token.
- includeThisToken, err := f.GetBool()
- if err != nil {
- return "", err
- }
- if !includeThisToken {
- continue
- }
- // There may be several tokens to choose from:
- if len(tokens) > 1 {
- chosenToken, err := chooseToken(tokens, f)
- if err != nil {
- return "", err
- }
- query.WriteString(" " + chosenToken)
- } else {
- token.WriteString(tokens[0])
- // In case the token is "CUSTOM_FUZZ_STRING"
- // we will then create a non-structured string
- if token.String() == "CUSTOM_FUZZ_STRING" {
- customFuzzString, err := f.GetString()
- if err != nil {
- return "", err
- }
- query.WriteString(" " + customFuzzString)
- continue
- }
- // Check if token requires an argument.
- // Tokens that take an argument can be found
- // in 'needCustomString'. If so, we add a
- // non-structured string to the token.
- if containsString(needCustomString, token.String()) {
- customFuzzString, err := f.GetString()
- if err != nil {
- return "", err
- }
- token.WriteString(fmt.Sprintf(" %s", customFuzzString))
- }
- query.WriteString(fmt.Sprintf(" %s", token.String()))
- }
- }
- return query.String(), nil
- }
- // Creates a semi-structured query. It creates a string
- // that is a combination of the keywords and random strings.
- func createQuery(f *ConsumeFuzzer) (string, error) {
- queryLen, err := f.GetInt()
- if err != nil {
- return "", err
- }
- maxLen := queryLen % 60
- if maxLen == 0 {
- return "", fmt.Errorf("could not create a query")
- }
- var query strings.Builder
- for i := 0; i < maxLen; i++ {
- // Get a new token:
- useKeyword, err := f.GetBool()
- if err != nil {
- return "", err
- }
- if useKeyword {
- keyword, err := getKeyword(f)
- if err != nil {
- return "", err
- }
- query.WriteString(" " + keyword)
- } else {
- customString, err := f.GetString()
- if err != nil {
- return "", err
- }
- query.WriteString(" " + customString)
- }
- }
- if query.String() == "" {
- return "", fmt.Errorf("could not create a query")
- }
- return query.String(), nil
- }
- // GetSQLString is the API that users interact with.
- //
- // Usage:
- //
- // f := NewConsumer(data)
- // sqlString, err := f.GetSQLString()
- func (f *ConsumeFuzzer) GetSQLString() (string, error) {
- var query string
- veryStructured, err := f.GetBool()
- if err != nil {
- return "", err
- }
- if veryStructured {
- query, err = createStmt(f)
- if err != nil {
- return "", err
- }
- } else {
- query, err = createQuery(f)
- if err != nil {
- return "", err
- }
- }
- return query, nil
- }
|