vitess.io/vitess@v0.16.2/go/test/fuzzing/vtctl_fuzzer.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package fuzzing 18 19 import ( 20 "context" 21 "strings" 22 23 "vitess.io/vitess/go/vt/logutil" 24 "vitess.io/vitess/go/vt/topo" 25 "vitess.io/vitess/go/vt/topo/memorytopo" 26 "vitess.io/vitess/go/vt/vtctl" 27 "vitess.io/vitess/go/vt/vttablet/tmclient" 28 "vitess.io/vitess/go/vt/vttablet/tmclienttest" 29 "vitess.io/vitess/go/vt/wrangler" 30 ) 31 32 func init() { 33 tmclienttest.SetProtocol("go.test.fuzzing.vtctl_fuzzer", "fuzzing") 34 tmclient.RegisterTabletManagerClientFactory("fuzzing", func() tmclient.TabletManagerClient { 35 return nil 36 }) 37 } 38 39 func IsDivisibleBy(n int, divisibleby int) bool { 40 return (n % divisibleby) == 0 41 } 42 43 func getCommandType(index int) string { 44 45 m := map[int]string{ 46 0: "GetTablet", // Tablets 47 1: "InitTablet", 48 2: "UpdateTabletAddrs", 49 3: "DeleteTablet", 50 4: "SetReadOnly", 51 5: "SetReadWrite", 52 6: "StartReplication", 53 7: "StopReplication", 54 8: "ChangeTabletType", 55 9: "Ping", 56 10: "RefreshState", 57 11: "RefreshStateByShard", 58 12: "RunHealthCheck", 59 13: "IgnoreHealthCheck", 60 14: "IgnoreHealthError", 61 15: "ExecuteHook", 62 16: "ExecuteFetchAsApp", 63 17: "ExecuteFetchAsDba", 64 18: "VReplicationExec", 65 19: "Backup", 66 20: "RestoreFromBackup", 67 21: "ReparentTablet", 68 22: "CreateShard", // Shards 69 23: "GetShard", 70 24: "ValidateShard", 71 25: "ShardReplicationPositions", 72 26: "ListShardTablets", 73 27: "SetShardIsPrimaryServing", 74 28: "SetShardTabletControl", 75 29: "UpdateSrvKeyspacePartition", 76 30: "SourceShardDelete", 77 31: "SourceShardAdd", 78 32: "ShardReplicationFix", 79 33: "WaitForFilteredReplication", 80 34: "RemoveShardCell", 81 35: "DeleteShard", 82 36: "ListBackups", 83 37: "BackupShard", 84 38: "RemoveBackup", 85 39: "InitShardPrimary", 86 40: "PlannedReparentShard", 87 41: "EmergencyReparentShard", 88 42: "TabletExternallyReparented", 89 43: "CreateKeyspace", // Keyspaces 90 44: "DeleteKeyspace", 91 45: "RemoveKeyspaceCell", 92 46: "GetKeyspace", 93 47: "GetKeyspaces", 94 50: "RebuildKeyspaceGraph", 95 51: "ValidateKeyspace", 96 52: "Reshard", 97 53: "MoveTables", 98 55: "CreateLookupVindex", 99 56: "ExternalizeVindex", 100 57: "Materialize", 101 60: "VDiff", 102 63: "SwitchTraffic", 103 67: "FindAllShardsInKeyspace", 104 } 105 return m[index] 106 107 } 108 109 /* 110 In this fuzzer we split the input into 3 chunks: 111 1: the first byte - Is converted to an int, and 112 113 that int determines the number of command-line 114 calls the fuzzer will make. 115 116 2: The next n bytes where n is equal to the int from 117 118 the first byte. These n bytes are converted to 119 a corresponding command and represent which 120 commands will be called. 121 122 3: The rest of the data array should have a length 123 124 that is divisible by the number of calls. 125 This part is split up into equally large chunks, 126 and each chunk is used as parameters for the 127 corresponding command. 128 */ 129 func Fuzz(data []byte) int { 130 131 // Basic checks 132 if len(data) == 0 { 133 return -1 134 } 135 numberOfCalls := int(data[0]) 136 if numberOfCalls < 3 || numberOfCalls > 10 { 137 return -1 138 } 139 if len(data) < numberOfCalls+numberOfCalls+1 { 140 return -1 141 } 142 143 // Define part 2 and 3 of the data array 144 commandPart := data[1 : numberOfCalls+1] 145 restOfArray := data[numberOfCalls+1:] 146 147 // Just a small check. It is necessary 148 if len(commandPart) != numberOfCalls { 149 return -1 150 } 151 152 // Check if restOfArray is divisible by numberOfCalls 153 if !IsDivisibleBy(len(restOfArray), numberOfCalls) { 154 return -1 155 } 156 157 // At this point we have a data array that can 158 // be divided properly. We can now proceed to 159 // passing it to Vitess 160 ctx := context.Background() 161 topo, err := createTopo(ctx) 162 if err != nil { 163 return -1 164 } 165 tmc := tmclient.NewTabletManagerClient() 166 logger := logutil.NewMemoryLogger() 167 168 chunkSize := len(restOfArray) / numberOfCalls 169 command := 0 170 for i := 0; i < len(restOfArray); i = i + chunkSize { 171 from := i // lower 172 to := i + chunkSize // upper 173 174 // Index of command in getCommandType(): 175 commandIndex := int(commandPart[command]) % 68 176 vtCommand := getCommandType(commandIndex) 177 commandSlice := []string{vtCommand} 178 args := strings.Split(string(restOfArray[from:to]), " ") 179 180 // Add params to the command 181 commandSlice = append(commandSlice, args...) 182 183 _ = vtctl.RunCommand(ctx, wrangler.New(logger, topo, tmc), commandSlice) 184 command++ 185 } 186 187 return 1 188 189 } 190 191 func createTopo(ctx context.Context) (*topo.Server, error) { 192 ts := memorytopo.NewServer("zone1", "zone2", "zone3") 193 return ts, nil 194 }