fuzz.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /*
  2. Copyright 2022 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package spec
  14. import (
  15. "github.com/go-openapi/jsonreference"
  16. "github.com/google/go-cmp/cmp"
  17. fuzz "github.com/google/gofuzz"
  18. )
  19. var SwaggerFuzzFuncs []interface{} = []interface{}{
  20. func(v *Responses, c fuzz.Continue) {
  21. c.FuzzNoCustom(v)
  22. if v.Default != nil {
  23. // Check if we hit maxDepth and left an incomplete value
  24. if v.Default.Description == "" {
  25. v.Default = nil
  26. v.StatusCodeResponses = nil
  27. }
  28. }
  29. // conversion has no way to discern empty statusCodeResponses from
  30. // nil, since "default" is always included in the map.
  31. // So avoid empty responses list
  32. if len(v.StatusCodeResponses) == 0 {
  33. v.StatusCodeResponses = nil
  34. }
  35. },
  36. func(v *Operation, c fuzz.Continue) {
  37. c.FuzzNoCustom(v)
  38. if v != nil {
  39. // force non-nil
  40. v.Responses = &Responses{}
  41. c.Fuzz(v.Responses)
  42. v.Schemes = nil
  43. if c.RandBool() {
  44. v.Schemes = append(v.Schemes, "http")
  45. }
  46. if c.RandBool() {
  47. v.Schemes = append(v.Schemes, "https")
  48. }
  49. if c.RandBool() {
  50. v.Schemes = append(v.Schemes, "ws")
  51. }
  52. if c.RandBool() {
  53. v.Schemes = append(v.Schemes, "wss")
  54. }
  55. // Gnostic unconditionally makes security values non-null
  56. // So do not fuzz null values into the array.
  57. for i, val := range v.Security {
  58. if val == nil {
  59. v.Security[i] = make(map[string][]string)
  60. }
  61. for k, v := range val {
  62. if v == nil {
  63. val[k] = make([]string, 0)
  64. }
  65. }
  66. }
  67. }
  68. },
  69. func(v map[int]Response, c fuzz.Continue) {
  70. n := 0
  71. c.Fuzz(&n)
  72. if n == 0 {
  73. // Test that fuzzer is not at maxDepth so we do not
  74. // end up with empty elements
  75. return
  76. }
  77. // Prevent negative numbers
  78. num := c.Intn(4)
  79. for i := 0; i < num+2; i++ {
  80. val := Response{}
  81. c.Fuzz(&val)
  82. val.Description = c.RandString() + "x"
  83. v[100*(i+1)+c.Intn(100)] = val
  84. }
  85. },
  86. func(v map[string]PathItem, c fuzz.Continue) {
  87. n := 0
  88. c.Fuzz(&n)
  89. if n == 0 {
  90. // Test that fuzzer is not at maxDepth so we do not
  91. // end up with empty elements
  92. return
  93. }
  94. num := c.Intn(5)
  95. for i := 0; i < num+2; i++ {
  96. val := PathItem{}
  97. c.Fuzz(&val)
  98. // Ref params are only allowed in certain locations, so
  99. // possibly add a few to PathItems
  100. numRefsToAdd := c.Intn(5)
  101. for i := 0; i < numRefsToAdd; i++ {
  102. theRef := Parameter{}
  103. c.Fuzz(&theRef.Refable)
  104. val.Parameters = append(val.Parameters, theRef)
  105. }
  106. v["/"+c.RandString()] = val
  107. }
  108. },
  109. func(v *SchemaOrArray, c fuzz.Continue) {
  110. *v = SchemaOrArray{}
  111. // gnostic parser just doesn't support more
  112. // than one Schema here
  113. v.Schema = &Schema{}
  114. c.Fuzz(&v.Schema)
  115. },
  116. func(v *SchemaOrBool, c fuzz.Continue) {
  117. *v = SchemaOrBool{}
  118. if c.RandBool() {
  119. v.Allows = c.RandBool()
  120. } else {
  121. v.Schema = &Schema{}
  122. v.Allows = true
  123. c.Fuzz(&v.Schema)
  124. }
  125. },
  126. func(v map[string]Response, c fuzz.Continue) {
  127. n := 0
  128. c.Fuzz(&n)
  129. if n == 0 {
  130. // Test that fuzzer is not at maxDepth so we do not
  131. // end up with empty elements
  132. return
  133. }
  134. // Response definitions are not allowed to
  135. // be refs
  136. for i := 0; i < c.Intn(5)+1; i++ {
  137. resp := &Response{}
  138. c.Fuzz(resp)
  139. resp.Ref = Ref{}
  140. resp.Description = c.RandString() + "x"
  141. // Response refs are not vendor extensible by gnostic
  142. resp.VendorExtensible.Extensions = nil
  143. v[c.RandString()+"x"] = *resp
  144. }
  145. },
  146. func(v *Header, c fuzz.Continue) {
  147. if v != nil {
  148. c.FuzzNoCustom(v)
  149. // descendant Items of Header may not be refs
  150. cur := v.Items
  151. for cur != nil {
  152. cur.Ref = Ref{}
  153. cur = cur.Items
  154. }
  155. }
  156. },
  157. func(v *Ref, c fuzz.Continue) {
  158. *v = Ref{}
  159. v.Ref, _ = jsonreference.New("http://asd.com/" + c.RandString())
  160. },
  161. func(v *Response, c fuzz.Continue) {
  162. *v = Response{}
  163. if c.RandBool() {
  164. v.Ref = Ref{}
  165. v.Ref.Ref, _ = jsonreference.New("http://asd.com/" + c.RandString())
  166. } else {
  167. c.Fuzz(&v.VendorExtensible)
  168. c.Fuzz(&v.Schema)
  169. c.Fuzz(&v.ResponseProps)
  170. v.Headers = nil
  171. v.Ref = Ref{}
  172. n := 0
  173. c.Fuzz(&n)
  174. if n != 0 {
  175. // Test that fuzzer is not at maxDepth so we do not
  176. // end up with empty elements
  177. num := c.Intn(4)
  178. for i := 0; i < num; i++ {
  179. if v.Headers == nil {
  180. v.Headers = make(map[string]Header)
  181. }
  182. hdr := Header{}
  183. c.Fuzz(&hdr)
  184. if hdr.Type == "" {
  185. // hit maxDepth, just abort trying to make haders
  186. v.Headers = nil
  187. break
  188. }
  189. v.Headers[c.RandString()+"x"] = hdr
  190. }
  191. } else {
  192. v.Headers = nil
  193. }
  194. }
  195. v.Description = c.RandString() + "x"
  196. // Gnostic parses empty as nil, so to keep avoid putting empty
  197. if len(v.Headers) == 0 {
  198. v.Headers = nil
  199. }
  200. },
  201. func(v **Info, c fuzz.Continue) {
  202. // Info is never nil
  203. *v = &Info{}
  204. c.FuzzNoCustom(*v)
  205. (*v).Title = c.RandString() + "x"
  206. },
  207. func(v *Extensions, c fuzz.Continue) {
  208. // gnostic parser only picks up x- vendor extensions
  209. numChildren := c.Intn(5)
  210. for i := 0; i < numChildren; i++ {
  211. if *v == nil {
  212. *v = Extensions{}
  213. }
  214. (*v)["x-"+c.RandString()] = c.RandString()
  215. }
  216. },
  217. func(v *Swagger, c fuzz.Continue) {
  218. c.FuzzNoCustom(v)
  219. if v.Paths == nil {
  220. // Force paths non-nil since it does not have omitempty in json tag.
  221. // This means a perfect roundtrip (via json) is impossible,
  222. // since we can't tell the difference between empty/unspecified paths
  223. v.Paths = &Paths{}
  224. c.Fuzz(v.Paths)
  225. }
  226. v.Swagger = "2.0"
  227. // Gnostic support serializing ID at all
  228. // unavoidable data loss
  229. v.ID = ""
  230. v.Schemes = nil
  231. if c.RandUint64()%2 == 1 {
  232. v.Schemes = append(v.Schemes, "http")
  233. }
  234. if c.RandUint64()%2 == 1 {
  235. v.Schemes = append(v.Schemes, "https")
  236. }
  237. if c.RandUint64()%2 == 1 {
  238. v.Schemes = append(v.Schemes, "ws")
  239. }
  240. if c.RandUint64()%2 == 1 {
  241. v.Schemes = append(v.Schemes, "wss")
  242. }
  243. // Gnostic unconditionally makes security values non-null
  244. // So do not fuzz null values into the array.
  245. for i, val := range v.Security {
  246. if val == nil {
  247. v.Security[i] = make(map[string][]string)
  248. }
  249. for k, v := range val {
  250. if v == nil {
  251. val[k] = make([]string, 0)
  252. }
  253. }
  254. }
  255. },
  256. func(v *SecurityScheme, c fuzz.Continue) {
  257. v.Description = c.RandString() + "x"
  258. c.Fuzz(&v.VendorExtensible)
  259. switch c.Intn(3) {
  260. case 0:
  261. v.Type = "basic"
  262. case 1:
  263. v.Type = "apiKey"
  264. switch c.Intn(2) {
  265. case 0:
  266. v.In = "header"
  267. case 1:
  268. v.In = "query"
  269. default:
  270. panic("unreachable")
  271. }
  272. v.Name = "x" + c.RandString()
  273. case 2:
  274. v.Type = "oauth2"
  275. switch c.Intn(4) {
  276. case 0:
  277. v.Flow = "accessCode"
  278. v.TokenURL = "https://" + c.RandString()
  279. v.AuthorizationURL = "https://" + c.RandString()
  280. case 1:
  281. v.Flow = "application"
  282. v.TokenURL = "https://" + c.RandString()
  283. case 2:
  284. v.Flow = "implicit"
  285. v.AuthorizationURL = "https://" + c.RandString()
  286. case 3:
  287. v.Flow = "password"
  288. v.TokenURL = "https://" + c.RandString()
  289. default:
  290. panic("unreachable")
  291. }
  292. c.Fuzz(&v.Scopes)
  293. default:
  294. panic("unreachable")
  295. }
  296. },
  297. func(v *interface{}, c fuzz.Continue) {
  298. *v = c.RandString() + "x"
  299. },
  300. func(v *string, c fuzz.Continue) {
  301. *v = c.RandString() + "x"
  302. },
  303. func(v *ExternalDocumentation, c fuzz.Continue) {
  304. v.Description = c.RandString() + "x"
  305. v.URL = c.RandString() + "x"
  306. },
  307. func(v *SimpleSchema, c fuzz.Continue) {
  308. c.FuzzNoCustom(v)
  309. switch c.Intn(5) {
  310. case 0:
  311. v.Type = "string"
  312. case 1:
  313. v.Type = "number"
  314. case 2:
  315. v.Type = "boolean"
  316. case 3:
  317. v.Type = "integer"
  318. case 4:
  319. v.Type = "array"
  320. default:
  321. panic("unreachable")
  322. }
  323. switch c.Intn(5) {
  324. case 0:
  325. v.CollectionFormat = "csv"
  326. case 1:
  327. v.CollectionFormat = "ssv"
  328. case 2:
  329. v.CollectionFormat = "tsv"
  330. case 3:
  331. v.CollectionFormat = "pipes"
  332. case 4:
  333. v.CollectionFormat = ""
  334. default:
  335. panic("unreachable")
  336. }
  337. // None of the types which include SimpleSchema in our definitions
  338. // actually support "example" in the official spec
  339. v.Example = nil
  340. // unsupported by openapi
  341. v.Nullable = false
  342. },
  343. func(v *int64, c fuzz.Continue) {
  344. c.Fuzz(v)
  345. // Gnostic does not differentiate between 0 and non-specified
  346. // so avoid using 0 for fuzzer
  347. if *v == 0 {
  348. *v = 1
  349. }
  350. },
  351. func(v *float64, c fuzz.Continue) {
  352. c.Fuzz(v)
  353. // Gnostic does not differentiate between 0 and non-specified
  354. // so avoid using 0 for fuzzer
  355. if *v == 0.0 {
  356. *v = 1.0
  357. }
  358. },
  359. func(v *Parameter, c fuzz.Continue) {
  360. if v == nil {
  361. return
  362. }
  363. c.Fuzz(&v.VendorExtensible)
  364. if c.RandBool() {
  365. // body param
  366. v.Description = c.RandString() + "x"
  367. v.Name = c.RandString() + "x"
  368. v.In = "body"
  369. c.Fuzz(&v.Description)
  370. c.Fuzz(&v.Required)
  371. v.Schema = &Schema{}
  372. c.Fuzz(&v.Schema)
  373. } else {
  374. c.Fuzz(&v.SimpleSchema)
  375. c.Fuzz(&v.CommonValidations)
  376. v.AllowEmptyValue = false
  377. v.Description = c.RandString() + "x"
  378. v.Name = c.RandString() + "x"
  379. switch c.Intn(4) {
  380. case 0:
  381. // Header param
  382. v.In = "header"
  383. case 1:
  384. // Form data param
  385. v.In = "formData"
  386. v.AllowEmptyValue = c.RandBool()
  387. case 2:
  388. // Query param
  389. v.In = "query"
  390. v.AllowEmptyValue = c.RandBool()
  391. case 3:
  392. // Path param
  393. v.In = "path"
  394. v.Required = true
  395. default:
  396. panic("unreachable")
  397. }
  398. // descendant Items of Parameter may not be refs
  399. cur := v.Items
  400. for cur != nil {
  401. cur.Ref = Ref{}
  402. cur = cur.Items
  403. }
  404. }
  405. },
  406. func(v *Schema, c fuzz.Continue) {
  407. if c.RandBool() {
  408. // file schema
  409. c.Fuzz(&v.Default)
  410. c.Fuzz(&v.Description)
  411. c.Fuzz(&v.Example)
  412. c.Fuzz(&v.ExternalDocs)
  413. c.Fuzz(&v.Format)
  414. c.Fuzz(&v.ReadOnly)
  415. c.Fuzz(&v.Required)
  416. c.Fuzz(&v.Title)
  417. v.Type = StringOrArray{"file"}
  418. } else {
  419. // normal schema
  420. c.Fuzz(&v.SchemaProps)
  421. c.Fuzz(&v.SwaggerSchemaProps)
  422. c.Fuzz(&v.VendorExtensible)
  423. // c.Fuzz(&v.ExtraProps)
  424. // ExtraProps will not roundtrip - gnostic throws out
  425. // unrecognized keys
  426. }
  427. // Not supported by official openapi v2 spec
  428. // and stripped by k8s apiserver
  429. v.ID = ""
  430. v.AnyOf = nil
  431. v.OneOf = nil
  432. v.Not = nil
  433. v.Nullable = false
  434. v.AdditionalItems = nil
  435. v.Schema = ""
  436. v.PatternProperties = nil
  437. v.Definitions = nil
  438. v.Dependencies = nil
  439. },
  440. }
  441. var SwaggerDiffOptions = []cmp.Option{
  442. // cmp.Diff panics on Ref since jsonreference.Ref uses unexported fields
  443. cmp.Comparer(func(a Ref, b Ref) bool {
  444. return a.String() == b.String()
  445. }),
  446. }