├── .gitignore ├── LICENSE ├── docs ├── fuzztarget.html └── nimdoc.out.css ├── examples ├── base64 │ ├── decode.nim │ └── roundtrip.nim ├── compress │ ├── compress.nim │ └── compress.nims ├── fpsum.nim └── imgread.nim ├── experiments ├── altapi.nim ├── api_exp.nim ├── memstreams.nim ├── nfpsum.nim ├── nfpsum.nims └── tstructure.nim ├── libfuzzer.nimble ├── libfuzzer ├── fuzztarget.nim ├── fuzztarget.nims └── standalone.nim ├── readme.rst └── tests ├── shadowmem.nim ├── tasan.nim ├── tcmdline.nim ├── tcov.nim ├── tcrossover.nim ├── tcycleleak.nim ├── tdefect.nim ├── tfuzz.nim ├── tmem.nim ├── traise.nim ├── tstring.nim └── ttsan.nim /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all root files 2 | /* 3 | # Exclude source code files in subdirectories 4 | !/*.nim 5 | !/*.nims 6 | !/*.rst 7 | # Exclude root directories 8 | !/*/ 9 | # Include all files in subdirectories 10 | /*/* 11 | # Exclude source code files in subdirectories 12 | !/*/*.nim 13 | !/*/*.nims 14 | # Exclude doc files in subdirectory 15 | !/docs/*.html 16 | !/docs/*.css 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Antonis Geralis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/fuzztarget.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | libfuzzer/fuzztarget 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |

libfuzzer/fuzztarget

24 |
25 |
26 |
27 | 28 | 33 |
34 | 39 |
40 | Search: 41 |
42 |
43 | Group by: 44 | 48 |
49 | 88 | 89 |
90 |
91 | Source   92 | Edit   93 | 94 |
95 | 96 |

NOTE: the libFuzzer interface is thin and in the majority of cases all you need is to define the procedure testOneInput in your file.

97 |
98 |

Procs

99 |
100 |
101 |
102 |
proc customCrossOver(data1: ptr UncheckedArray[byte]; len1: int;
103 |                      data2: ptr UncheckedArray[byte]; len2: int;
104 |                      res: ptr UncheckedArray[byte]; maxResLen: int; seed: int64): int {.
105 |     exportc: "LLVMFuzzerCustomCrossOver", ...raises: [], tags: [], forbids: [].}
106 |
107 | 108 | Optional user-provided custom cross-over procedure. Combines pieces of data1 & data2 together into res. Returns the new length, which is not greater than maxResLen. Should produce the same mutation given the same seed. 109 | Source   110 | Edit   111 | 112 |
113 |
114 | 115 |
116 |
117 |
118 |
proc customMutator(data: ptr UncheckedArray[byte]; len, maxLen: int; seed: int64): int {.
119 |     exportc: "LLVMFuzzerCustomMutator", ...raises: [], tags: [], forbids: [].}
120 |
121 | 122 | Optional user-provided custom mutator. Mutates raw data in data[0..<len] inplace. Returns the new length, which is not greater than maxLen. Given the same seed produces the same mutation. 123 | Source   124 | Edit   125 | 126 |
127 |
128 | 129 |
130 |
131 |
132 |
proc initialize(): cint {.exportc: "LLVMFuzzerInitialize", ...raises: [], tags: [],
133 |                           forbids: [].}
134 |
135 | 136 | Initialize Nim's internals, which is done calling a NimMain function. 137 | Source   138 | Edit   139 | 140 |
141 |
142 | 143 |
144 |
145 |
146 |
proc mutate(data: ptr UncheckedArray[byte]; len, maxLen: int): int {.
147 |     importc: "LLVMFuzzerMutate", ...raises: [], tags: [], forbids: [].}
148 |
149 | 150 | Experimental, may go away in future. libFuzzer-provided procedure to be used inside customMutator. Mutates raw data in data[0..<len] inplace. Returns the new length, which is not greater than maxLen. 151 | Source   152 | Edit   153 | 154 |
155 |
156 | 157 |
158 |
159 |
160 |
proc standaloneFuzzTarget() {....raises: [IOError, ValueError],
161 |                               tags: [WriteIOEffect, ReadIOEffect], forbids: [].}
162 |
163 | 164 |

Standalone main procedure for fuzz targets.

165 |

Use -d:fuzzSa to call standaloneFuzzTarget to provide reproducers for bugs when linking against libFuzzer is undesirable.

166 | 167 | Source   168 | Edit   169 | 170 |
171 |
172 | 173 |
174 |
175 |
176 |
proc testOneInput(data: ptr UncheckedArray[byte]; len: int): cint {.
177 |     exportc: "LLVMFuzzerTestOneInput", ...raises: [], tags: [], forbids: [].}
178 |
179 | 180 | Mandatory user-provided target procedure. Executes the code under test with data as the input. libFuzzer will invoke this procedure many times with different inputs. Must return 0. 181 | Source   182 | Edit   183 | 184 |
185 |
186 | 187 |
188 | 189 |
190 |
191 | 192 |
193 |
194 | 195 | 200 |
201 |
202 | 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /docs/nimdoc.out.css: -------------------------------------------------------------------------------- 1 | /* 2 | Stylesheet for use with Docutils/rst2html. 3 | 4 | See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to 5 | customize this style sheet. 6 | 7 | Modified from Chad Skeeters' rst2html-style 8 | https://bitbucket.org/cskeeters/rst2html-style/ 9 | 10 | Modified by Boyd Greenfield and narimiran 11 | */ 12 | 13 | :root { 14 | --primary-background: #fff; 15 | --secondary-background: ghostwhite; 16 | --third-background: #e8e8e8; 17 | --info-background: #50c050; 18 | --warning-background: #c0a000; 19 | --error-background: #e04040; 20 | --border: #dde; 21 | --text: #222; 22 | --anchor: #07b; 23 | --anchor-focus: #607c9f; 24 | --input-focus: #1fa0eb; 25 | --strong: #3c3c3c; 26 | --hint: #9A9A9A; 27 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAN4AAAA9CAYAAADCt9ebAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjAzOjQ4KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1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"); 28 | 29 | --keyword: #5e8f60; 30 | --identifier: #222; 31 | --comment: #484a86; 32 | --operator: #155da4; 33 | --punctuation: black; 34 | --other: black; 35 | --escapeSequence: #c4891b; 36 | --number: #252dbe; 37 | --literal: #a4255b; 38 | --program: #6060c0; 39 | --option: #508000; 40 | --raw-data: #a4255b; 41 | 42 | --clipboard-image-normal: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: black' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E %3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' /%3E %3C/svg%3E"); 43 | --clipboard-image-selected: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: black' viewBox='0 0 20 20' fill='currentColor'%3E %3Cpath d='M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z' /%3E %3Cpath d='M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z' /%3E %3C/svg%3E"); 44 | --clipboard-image: var(--clipboard-image-normal) 45 | } 46 | 47 | [data-theme="dark"] { 48 | --primary-background: #171921; 49 | --secondary-background: #1e202a; 50 | --third-background: #2b2e3b; 51 | --info-background: #008000; 52 | --warning-background: #807000; 53 | --error-background: #c03000; 54 | --border: #0e1014; 55 | --text: #fff; 56 | --anchor: #8be9fd; 57 | --anchor-focus: #8be9fd; 58 | --input-focus: #8be9fd; 59 | --strong: #bd93f9; 60 | --hint: #7A7C85; 61 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); 62 | 63 | --keyword: #ff79c6; 64 | --identifier: #f8f8f2; 65 | --comment: #6272a4; 66 | --operator: #ff79c6; 67 | --punctuation: #f8f8f2; 68 | --other: #f8f8f2; 69 | --escapeSequence: #bd93f9; 70 | --number: #bd93f9; 71 | --literal: #f1fa8c; 72 | --program: #9090c0; 73 | --option: #90b010; 74 | --raw-data: #8be9fd; 75 | 76 | --clipboard-image-normal: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: lightgray' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E %3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' /%3E %3C/svg%3E"); 77 | --clipboard-image-selected: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: lightgray' viewBox='0 0 20 20' fill='currentColor'%3E %3Cpath d='M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z' /%3E %3Cpath d='M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z' /%3E %3C/svg%3E"); 78 | --clipboard-image: var(--clipboard-image-normal); 79 | } 80 | 81 | @media (prefers-color-scheme: dark) { 82 | [data-theme="auto"] { 83 | --primary-background: #171921; 84 | --secondary-background: #1e202a; 85 | --third-background: #2b2e3b; 86 | --info-background: #008000; 87 | --warning-background: #807000; 88 | --error-background: #c03000; 89 | --border: #0e1014; 90 | --text: #fff; 91 | --anchor: #8be9fd; 92 | --anchor-focus: #8be9fd; 93 | --input-focus: #8be9fd; 94 | --strong: #bd93f9; 95 | --hint: #7A7C85; 96 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); 97 | 98 | --keyword: #ff79c6; 99 | --identifier: #f8f8f2; 100 | --comment: #6272a4; 101 | --operator: #ff79c6; 102 | --punctuation: #f8f8f2; 103 | --other: #f8f8f2; 104 | --escapeSequence: #bd93f9; 105 | --number: #bd93f9; 106 | --literal: #f1fa8c; 107 | --program: #9090c0; 108 | --option: #90b010; 109 | --raw-data: #8be9fd; 110 | 111 | --clipboard-image-normal: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: lightgray' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E %3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' /%3E %3C/svg%3E"); 112 | --clipboard-image-selected: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: lightgray' viewBox='0 0 20 20' fill='currentColor'%3E %3Cpath d='M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z' /%3E %3Cpath d='M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z' /%3E %3C/svg%3E"); 113 | --clipboard-image: var(--clipboard-image-normal); 114 | } 115 | } 116 | 117 | .theme-select-wrapper { 118 | display: flex; 119 | align-items: center; 120 | } 121 | 122 | html { 123 | font-size: 100%; 124 | -webkit-text-size-adjust: 100%; 125 | -ms-text-size-adjust: 100%; } 126 | 127 | body { 128 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 129 | font-weight: 400; 130 | font-size: 1.125em; 131 | line-height: 1.5; 132 | color: var(--text); 133 | background-color: var(--primary-background); } 134 | 135 | /* Skeleton grid */ 136 | .container { 137 | position: relative; 138 | width: 100%; 139 | max-width: 1050px; 140 | margin: 0 auto; 141 | padding: 0; 142 | box-sizing: border-box; } 143 | 144 | .column, .columns { 145 | width: 100%; 146 | float: left; 147 | box-sizing: border-box; 148 | margin-left: 1%; } 149 | 150 | @media print { 151 | #global-links, .link-seesrc, .theme-switch-wrapper, #searchInputDiv, .search-groupby { 152 | display:none; 153 | } 154 | .columns { 155 | width:100% !important; 156 | } 157 | } 158 | 159 | .column:first-child, .columns:first-child { 160 | margin-left: 0; } 161 | 162 | .container .row { 163 | display: flex; } 164 | 165 | .three.columns { 166 | width: 25.0%; 167 | height: 100vh; 168 | position: sticky; 169 | top: 0px; 170 | overflow-y: auto; 171 | padding: 2px; 172 | } 173 | 174 | .nine.columns { 175 | width: 75.0%; 176 | padding-left: 1.5em; } 177 | 178 | .twelve.columns { 179 | width: 100%; 180 | margin-left: 0; } 181 | 182 | @media screen and (max-width: 860px) { 183 | .three.columns { 184 | display: none; 185 | } 186 | .nine.columns { 187 | width: 98.0%; 188 | } 189 | body { 190 | font-size: 1em; 191 | line-height: 1.35; 192 | } 193 | } 194 | 195 | cite { 196 | font-style: italic !important; } 197 | 198 | 199 | /* Nim search input */ 200 | div#searchInputDiv { 201 | margin-bottom: 1em; 202 | } 203 | input#searchInput { 204 | width: 80%; 205 | } 206 | 207 | /* 208 | * Some custom formatting for input forms. 209 | * This also fixes input form colors on Firefox with a dark system theme on Linux. 210 | */ 211 | input { 212 | -moz-appearance: none; 213 | background-color: var(--secondary-background); 214 | color: var(--text); 215 | border: 1px solid var(--border); 216 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 217 | font-size: 0.9em; 218 | padding: 6px; 219 | } 220 | 221 | input:focus { 222 | border: 1px solid var(--input-focus); 223 | box-shadow: 0 0 3px var(--input-focus); 224 | } 225 | 226 | select { 227 | -moz-appearance: none; 228 | background-color: var(--secondary-background); 229 | color: var(--text); 230 | border: 1px solid var(--border); 231 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 232 | font-size: 0.9em; 233 | padding: 6px; 234 | } 235 | 236 | select:focus { 237 | border: 1px solid var(--input-focus); 238 | box-shadow: 0 0 3px var(--input-focus); 239 | } 240 | 241 | /* Docgen styles */ 242 | 243 | :target { 244 | border: 2px solid #B5651D; 245 | border-style: dotted; 246 | } 247 | 248 | /* Links */ 249 | a { 250 | color: var(--anchor); 251 | text-decoration: none; 252 | } 253 | 254 | a span.Identifier { 255 | text-decoration: underline; 256 | text-decoration-color: #aab; 257 | } 258 | 259 | a.reference-toplevel { 260 | font-weight: bold; 261 | } 262 | 263 | a.nimdoc { 264 | word-spacing: 0.3em; 265 | } 266 | 267 | a.toc-backref { 268 | text-decoration: none; 269 | color: var(--text); 270 | } 271 | 272 | a.link-seesrc { 273 | color: #607c9f; 274 | font-size: 0.9em; 275 | font-style: italic; 276 | } 277 | 278 | a:hover, a:focus { 279 | color: var(--anchor-focus); 280 | text-decoration: underline; 281 | } 282 | 283 | a:hover span.Identifier { 284 | color: var(--anchor); 285 | } 286 | 287 | 288 | sub, sup { 289 | position: relative; 290 | font-size: 75%; 291 | line-height: 0; 292 | vertical-align: baseline; } 293 | 294 | sup { 295 | top: -0.5em; } 296 | 297 | sub { 298 | bottom: -0.25em; } 299 | 300 | img { 301 | width: auto; 302 | height: auto; 303 | max-width: 100%; 304 | vertical-align: middle; 305 | border: 0; 306 | -ms-interpolation-mode: bicubic; } 307 | 308 | @media print { 309 | * { 310 | color: black !important; 311 | text-shadow: none !important; 312 | background: transparent !important; 313 | box-shadow: none !important; } 314 | 315 | a, a:visited { 316 | text-decoration: underline; } 317 | 318 | a[href]:after { 319 | content: " (" attr(href) ")"; } 320 | 321 | abbr[title]:after { 322 | content: " (" attr(title) ")"; } 323 | 324 | .ir a:after, 325 | a[href^="javascript:"]:after, 326 | a[href^="#"]:after { 327 | content: ""; } 328 | 329 | pre, blockquote { 330 | border: 1px solid #999; 331 | page-break-inside: avoid; } 332 | 333 | thead { 334 | display: table-header-group; } 335 | 336 | tr, img { 337 | page-break-inside: avoid; } 338 | 339 | img { 340 | max-width: 100% !important; } 341 | 342 | @page { 343 | margin: 0.5cm; } 344 | 345 | h1 { 346 | page-break-before: always; } 347 | 348 | h1.title { 349 | page-break-before: avoid; } 350 | 351 | p, h2, h3 { 352 | orphans: 3; 353 | widows: 3; } 354 | 355 | h2, h3 { 356 | page-break-after: avoid; } 357 | } 358 | 359 | 360 | p { 361 | margin-top: 0.5em; 362 | margin-bottom: 0.5em; } 363 | 364 | small { 365 | font-size: 85%; } 366 | 367 | strong { 368 | font-weight: 600; 369 | font-size: 0.95em; 370 | color: var(--strong); } 371 | 372 | em { 373 | font-style: italic; } 374 | 375 | h1 { 376 | font-size: 1.8em; 377 | font-weight: 400; 378 | padding-bottom: .25em; 379 | border-bottom: 6px solid var(--third-background); 380 | margin-top: 2.5em; 381 | margin-bottom: 1em; 382 | line-height: 1.2em; } 383 | 384 | h1.title { 385 | padding-bottom: 1em; 386 | border-bottom: 0px; 387 | font-size: 2.5em; 388 | text-align: center; 389 | font-weight: 900; 390 | margin-top: 0.75em; 391 | margin-bottom: 0em; } 392 | 393 | h2 { 394 | font-size: 1.3em; 395 | margin-top: 2em; } 396 | 397 | h2.subtitle { 398 | margin-top: 0em; 399 | text-align: center; } 400 | 401 | h3 { 402 | font-size: 1.125em; 403 | font-style: italic; 404 | margin-top: 1.5em; } 405 | 406 | h4 { 407 | font-size: 1.125em; 408 | margin-top: 1em; } 409 | 410 | h5 { 411 | font-size: 1.125em; 412 | margin-top: 0.75em; } 413 | 414 | h6 { 415 | font-size: 1.1em; } 416 | 417 | 418 | ul, ol { 419 | padding: 0; 420 | margin-top: 0.5em; 421 | margin-left: 0.75em; } 422 | 423 | ul ul, ul ol, ol ol, ol ul { 424 | margin-bottom: 0; 425 | margin-left: 1.25em; } 426 | 427 | ul.simple > li { 428 | list-style-type: circle; } 429 | 430 | ul.simple-boot li { 431 | list-style-type: none; 432 | margin-left: 0em; 433 | margin-bottom: 0.5em; } 434 | 435 | ol.simple > li, ul.simple > li { 436 | margin-bottom: 0.2em; 437 | margin-left: 0.4em } 438 | 439 | ul.simple.simple-toc > li { 440 | margin-top: 1em; } 441 | 442 | ul.simple-toc { 443 | list-style: none; 444 | font-size: 0.9em; 445 | margin-left: -0.3em; 446 | margin-top: 1em; } 447 | 448 | ul.simple-toc > li { 449 | list-style-type: none; } 450 | 451 | ul.simple-toc-section { 452 | list-style-type: circle; 453 | margin-left: 0.8em; 454 | color: #6c9aae; } 455 | 456 | ul.nested-toc-section { 457 | list-style-type: circle; 458 | margin-left: -0.75em; 459 | color: var(--text); } 460 | 461 | ul.nested-toc-section > li { 462 | margin-left: 1.25em; } 463 | 464 | 465 | ol.arabic { 466 | list-style: decimal; } 467 | 468 | ol.loweralpha { 469 | list-style: lower-alpha; } 470 | 471 | ol.upperalpha { 472 | list-style: upper-alpha; } 473 | 474 | ol.lowerroman { 475 | list-style: lower-roman; } 476 | 477 | ol.upperroman { 478 | list-style: upper-roman; } 479 | 480 | ul.auto-toc { 481 | list-style-type: none; } 482 | 483 | 484 | dl { 485 | margin-bottom: 1.5em; } 486 | 487 | dt { 488 | margin-bottom: -0.5em; 489 | margin-left: 0.0em; } 490 | 491 | dd { 492 | margin-left: 2.0em; 493 | margin-bottom: 3.0em; 494 | margin-top: 0.5em; } 495 | 496 | 497 | hr { 498 | margin: 2em 0; 499 | border: 0; 500 | border-top: 1px solid #aaa; } 501 | 502 | hr.footnote { 503 | width: 25%; 504 | border-top: 0.15em solid #999; 505 | margin-bottom: 0.15em; 506 | margin-top: 0.15em; 507 | } 508 | div.footnote-group { 509 | margin-left: 1em; 510 | } 511 | div.footnote-label { 512 | display: inline-block; 513 | min-width: 1.7em; 514 | } 515 | 516 | div.option-list { 517 | border: 0.1em solid var(--border); 518 | } 519 | div.option-list-item { 520 | padding-left: 12em; 521 | padding-right: 0; 522 | padding-bottom: 0.3em; 523 | padding-top: 0.3em; 524 | } 525 | div.odd { 526 | background-color: var(--secondary-background); 527 | } 528 | div.option-list-label { 529 | margin-left: -11.5em; 530 | margin-right: 0em; 531 | min-width: 11.5em; 532 | display: inline-block; 533 | vertical-align: top; 534 | } 535 | div.option-list-description { 536 | width: calc(100% - 1em); 537 | padding-left: 1em; 538 | padding-right: 0; 539 | display: inline-block; 540 | } 541 | 542 | blockquote { 543 | font-size: 0.9em; 544 | font-style: italic; 545 | padding-left: 0.5em; 546 | margin-left: 0; 547 | border-left: 5px solid #bbc; 548 | } 549 | 550 | blockquote.markdown-quote { 551 | font-size: 0.9rem; /* use rem to avoid recursion */ 552 | font-style: normal; 553 | } 554 | 555 | .pre, span.tok { 556 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 557 | font-weight: 500; 558 | font-size: 0.85em; 559 | color: var(--text); 560 | background-color: var(--third-background); 561 | padding-left: 3px; 562 | padding-right: 3px; 563 | border-radius: 4px; 564 | } 565 | 566 | span.tok { 567 | border: 1px solid #808080; 568 | padding-bottom: 0.1em; 569 | margin-right: 0.2em; 570 | } 571 | 572 | .copyToClipBoard { 573 | position: relative; 574 | } 575 | 576 | pre { 577 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 578 | color: var(--text); 579 | font-weight: 500; 580 | display: inline-block; 581 | box-sizing: border-box; 582 | min-width: 100%; 583 | padding: 0.5em; 584 | margin-top: 0.5em; 585 | margin-bottom: 0.5em; 586 | font-size: 0.85em; 587 | white-space: pre !important; 588 | overflow-y: hidden; 589 | overflow-x: visible; 590 | background-color: var(--secondary-background); 591 | border: 1px solid var(--border); 592 | -webkit-border-radius: 6px; 593 | -moz-border-radius: 6px; 594 | border-radius: 6px; 595 | } 596 | 597 | .copyToClipBoardBtn { 598 | visibility: hidden; 599 | position: absolute; 600 | width: 24px; 601 | border-radius: 4px; 602 | background-image: var(--clipboard-image); 603 | right: 5px; 604 | top: 13px; 605 | background-color: var(--secondary-background); 606 | padding: 11px; 607 | border: 0; 608 | } 609 | 610 | .copyToClipBoard:hover .copyToClipBoardBtn { 611 | visibility: visible; 612 | } 613 | 614 | .pre-scrollable { 615 | max-height: 340px; 616 | overflow-y: scroll; } 617 | 618 | 619 | /* Nim line-numbered tables */ 620 | .line-nums-table { 621 | width: 100%; 622 | table-layout: fixed; } 623 | 624 | table.line-nums-table { 625 | border-radius: 4px; 626 | border: 1px solid #cccccc; 627 | background-color: ghostwhite; 628 | border-collapse: separate; 629 | margin-top: 15px; 630 | margin-bottom: 25px; } 631 | 632 | .line-nums-table tbody { 633 | border: none; } 634 | 635 | .line-nums-table td pre { 636 | border: none; 637 | background-color: transparent; } 638 | 639 | .line-nums-table td.blob-line-nums { 640 | width: 28px; } 641 | 642 | .line-nums-table td.blob-line-nums pre { 643 | color: #b0b0b0; 644 | -webkit-filter: opacity(75%); 645 | filter: opacity(75%); 646 | text-align: right; 647 | border-color: transparent; 648 | background-color: transparent; 649 | padding-left: 0px; 650 | margin-left: 0px; 651 | padding-right: 0px; 652 | margin-right: 0px; } 653 | 654 | 655 | table { 656 | max-width: 100%; 657 | background-color: transparent; 658 | margin-top: 0.5em; 659 | margin-bottom: 1.5em; 660 | border-collapse: collapse; 661 | border-color: var(--third-background); 662 | border-spacing: 0; 663 | font-size: 0.9em; 664 | } 665 | 666 | table th, table td { 667 | padding: 0px 0.5em 0px; 668 | border-color: var(--third-background); 669 | } 670 | 671 | table th { 672 | background-color: var(--third-background); 673 | border-color: var(--third-background); 674 | font-weight: bold; } 675 | 676 | table th.docinfo-name { 677 | background-color: transparent; 678 | text-align: right; 679 | } 680 | 681 | table tr:hover { 682 | background-color: var(--third-background); } 683 | 684 | 685 | /* rst2html default used to remove borders from tables and images */ 686 | .borderless, table.borderless td, table.borderless th { 687 | border: 0; } 688 | 689 | table.borderless td, table.borderless th { 690 | /* Override padding for "table.docutils td" with "! important". 691 | The right padding separates the table cells. */ 692 | padding: 0 0.5em 0 0 !important; } 693 | 694 | .admonition { 695 | padding: 0.3em; 696 | background-color: var(--secondary-background); 697 | border-left: 0.4em solid #7f7f84; 698 | margin-bottom: 0.5em; 699 | -webkit-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); 700 | -moz-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); 701 | box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); 702 | } 703 | .admonition-info { 704 | border-color: var(--info-background); 705 | } 706 | .admonition-info-text { 707 | color: var(--info-background); 708 | } 709 | .admonition-warning { 710 | border-color: var(--warning-background); 711 | } 712 | .admonition-warning-text { 713 | color: var(--warning-background); 714 | } 715 | .admonition-error { 716 | border-color: var(--error-background); 717 | } 718 | .admonition-error-text { 719 | color: var(--error-background); 720 | } 721 | 722 | .first { 723 | /* Override more specific margin styles with "! important". */ 724 | margin-top: 0 !important; } 725 | 726 | .last, .with-subtitle { 727 | margin-bottom: 0 !important; } 728 | 729 | .hidden { 730 | display: none; } 731 | 732 | blockquote.epigraph { 733 | margin: 2em 5em; } 734 | 735 | dl.docutils dd { 736 | margin-bottom: 0.5em; } 737 | 738 | object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { 739 | overflow: hidden; } 740 | 741 | 742 | div.figure { 743 | margin-left: 2em; 744 | margin-right: 2em; } 745 | 746 | div.footer, div.header { 747 | clear: both; 748 | text-align: center; 749 | color: #666; 750 | font-size: smaller; } 751 | 752 | div.footer { 753 | padding-top: 5em; } 754 | 755 | div.line-block { 756 | display: block; 757 | margin-top: 1em; 758 | margin-bottom: 1em; } 759 | 760 | div.line-block div.line-block { 761 | margin-top: 0; 762 | margin-bottom: 0; 763 | margin-left: 1.5em; } 764 | 765 | div.topic { 766 | margin: 2em; } 767 | 768 | div.search_results { 769 | background-color: var(--third-background); 770 | margin: 3em; 771 | padding: 1em; 772 | border: 1px solid #4d4d4d; } 773 | 774 | div#global-links ul { 775 | margin-left: 0; 776 | list-style-type: none; } 777 | 778 | div#global-links > simple-boot { 779 | margin-left: 3em; } 780 | 781 | hr.docutils { 782 | width: 75%; } 783 | 784 | img.align-left, .figure.align-left, object.align-left { 785 | clear: left; 786 | float: left; 787 | margin-right: 1em; } 788 | 789 | img.align-right, .figure.align-right, object.align-right { 790 | clear: right; 791 | float: right; 792 | margin-left: 1em; } 793 | 794 | img.align-center, .figure.align-center, object.align-center { 795 | display: block; 796 | margin-left: auto; 797 | margin-right: auto; } 798 | 799 | .align-left { 800 | text-align: left; } 801 | 802 | .align-center { 803 | clear: both; 804 | text-align: center; } 805 | 806 | .align-right { 807 | text-align: right; } 808 | 809 | /* reset inner alignment in figures */ 810 | div.align-right { 811 | text-align: inherit; } 812 | 813 | p.attribution { 814 | text-align: right; 815 | margin-left: 50%; } 816 | 817 | p.caption { 818 | font-style: italic; } 819 | 820 | p.credits { 821 | font-style: italic; 822 | font-size: smaller; } 823 | 824 | p.label { 825 | white-space: nowrap; } 826 | 827 | p.rubric { 828 | font-weight: bold; 829 | font-size: larger; 830 | color: maroon; 831 | text-align: center; } 832 | 833 | p.topic-title { 834 | font-weight: bold; } 835 | 836 | pre.address { 837 | margin-bottom: 0; 838 | margin-top: 0; 839 | font: inherit; } 840 | 841 | pre.literal-block, pre.doctest-block, pre.math, pre.code { 842 | margin-left: 2em; 843 | margin-right: 2em; } 844 | 845 | pre.code .ln { 846 | color: grey; } 847 | 848 | /* line numbers */ 849 | pre.code, code { 850 | background-color: #eeeeee; } 851 | 852 | pre.code .comment, code .comment { 853 | color: #5c6576; } 854 | 855 | pre.code .keyword, code .keyword { 856 | color: #3B0D06; 857 | font-weight: bold; } 858 | 859 | pre.code .literal.string, code .literal.string { 860 | color: #0c5404; } 861 | 862 | pre.code .name.builtin, code .name.builtin { 863 | color: #352b84; } 864 | 865 | pre.code .deleted, code .deleted { 866 | background-color: #DEB0A1; } 867 | 868 | pre.code .inserted, code .inserted { 869 | background-color: #A3D289; } 870 | 871 | span.classifier { 872 | font-style: oblique; } 873 | 874 | span.classifier-delimiter { 875 | font-weight: bold; } 876 | 877 | span.problematic { 878 | color: #b30000; } 879 | 880 | span.section-subtitle { 881 | /* font-size relative to parent (h1..h6 element) */ 882 | font-size: 80%; } 883 | 884 | span.DecNumber { 885 | color: var(--number); } 886 | 887 | span.BinNumber { 888 | color: var(--number); } 889 | 890 | span.HexNumber { 891 | color: var(--number); } 892 | 893 | span.OctNumber { 894 | color: var(--number); } 895 | 896 | span.FloatNumber { 897 | color: var(--number); } 898 | 899 | span.Identifier { 900 | color: var(--identifier); } 901 | 902 | span.Keyword { 903 | font-weight: 600; 904 | color: var(--keyword); } 905 | 906 | span.StringLit { 907 | color: var(--literal); } 908 | 909 | span.LongStringLit { 910 | color: var(--literal); } 911 | 912 | span.CharLit { 913 | color: var(--literal); } 914 | 915 | span.EscapeSequence { 916 | color: var(--escapeSequence); } 917 | 918 | span.Operator { 919 | color: var(--operator); } 920 | 921 | span.Punctuation { 922 | color: var(--punctuation); } 923 | 924 | span.Comment, span.LongComment { 925 | font-style: italic; 926 | font-weight: 400; 927 | color: var(--comment); } 928 | 929 | span.RegularExpression { 930 | color: darkviolet; } 931 | 932 | span.TagStart { 933 | color: darkviolet; } 934 | 935 | span.TagEnd { 936 | color: darkviolet; } 937 | 938 | span.Key { 939 | color: #252dbe; } 940 | 941 | span.Value { 942 | color: #252dbe; } 943 | 944 | span.RawData { 945 | color: var(--raw-data); } 946 | 947 | span.Assembler { 948 | color: #252dbe; } 949 | 950 | span.Preprocessor { 951 | color: #252dbe; } 952 | 953 | span.Directive { 954 | color: #252dbe; } 955 | 956 | span.option { 957 | font-weight: bold; 958 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 959 | color: var(--option); } 960 | 961 | span.Prompt { 962 | font-weight: bold; 963 | color: red; } 964 | 965 | span.ProgramOutput { 966 | font-weight: bold; 967 | color: #808080; } 968 | 969 | span.program { 970 | font-weight: bold; 971 | color: var(--program); 972 | text-decoration: underline; 973 | text-decoration-color: var(--hint); 974 | text-decoration-thickness: 0.05em; 975 | text-underline-offset: 0.15em; } 976 | 977 | span.Command, span.Rule, span.Hyperlink, 978 | span.Label, span.Reference, span.Other { 979 | color: var(--other); } 980 | 981 | /* Pop type, const, proc, and iterator defs in nim def blocks */ 982 | dt pre > span.Identifier, dt pre > span.Operator { 983 | color: var(--identifier); 984 | font-weight: 700; } 985 | 986 | dt pre > span.Keyword ~ span.Identifier, dt pre > span.Identifier ~ span.Identifier, 987 | dt pre > span.Operator ~ span.Identifier, dt pre > span.Other ~ span.Identifier { 988 | color: var(--identifier); 989 | font-weight: inherit; } 990 | 991 | /* Nim sprite for the footer (taken from main page favicon) */ 992 | .nim-sprite { 993 | display: inline-block; 994 | width: 51px; 995 | height: 14px; 996 | background-position: 0 0; 997 | background-size: 51px 14px; 998 | -webkit-filter: opacity(50%); 999 | filter: opacity(50%); 1000 | background-repeat: no-repeat; 1001 | background-image: var(--nim-sprite-base64); 1002 | margin-bottom: 5px; } 1003 | 1004 | span.pragmadots { 1005 | /* Position: relative frees us up to make the dots 1006 | look really nice without fucking up the layout and 1007 | causing bulging in the parent container */ 1008 | position: relative; 1009 | /* 1px down looks slightly nicer */ 1010 | top: 1px; 1011 | padding: 2px; 1012 | background-color: var(--third-background); 1013 | border-radius: 4px; 1014 | margin: 0 2px; 1015 | cursor: pointer; 1016 | font-size: 0.8em; } 1017 | 1018 | span.pragmadots:hover { 1019 | background-color: var(--hint); } 1020 | 1021 | span.pragmawrap { 1022 | display: none; } 1023 | 1024 | span.attachedType { 1025 | display: none; 1026 | visibility: hidden; } 1027 | -------------------------------------------------------------------------------- /examples/base64/decode.nim: -------------------------------------------------------------------------------- 1 | # todo try to reason if this is needed 2 | import std/base64 3 | 4 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 5 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 6 | if len == 0: return 7 | var 8 | copy = newString(len) 9 | decoded: string 10 | copyMem(addr copy[0], data, len) 11 | try: decoded = decode(copy) except: return 12 | 13 | when defined(fuzzSa): 14 | include libfuzzer/standalone 15 | else: 16 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 17 | proc NimMain() {.importc: "NimMain".} 18 | NimMain() 19 | 20 | proc mutate(data: ptr UncheckedArray[byte]; len, maxLen: int): int {. 21 | importc: "LLVMFuzzerMutate".} 22 | 23 | proc customMutator(data: ptr UncheckedArray[byte]; len, maxLen: int, seed: int64): int {. 24 | exportc: "LLVMFuzzerCustomMutator", raises: [].} = 25 | # Decompress the input data. If that fails, use a dummy value. 26 | 27 | var copy = newString(len) 28 | copyMem(addr copy[0], data, len) 29 | var decoded = try: decode(copy) except: "hi" 30 | # Mutate the decoded data with `libFuzzer`'s default mutator. Expand 31 | # the `decompressed` seq's for inserting mutations via `grow`. 32 | let oldLen = decoded.len 33 | decoded.setLen(oldLen*2) 34 | let newDecodedLen = mutate(cast[ptr UncheckedArray[byte]](addr decoded[0]), 35 | oldLen, decoded.len) 36 | # Recompress the mutated data. 37 | var encoded = encode(decoded.toOpenArray(0, newDecodedLen-1)) 38 | # Copy the recompressed mutated data into `data` and return the new length. 39 | result = min(maxLen, encoded.len) 40 | copyMem(addr data[0], addr encoded[0], result) 41 | #for i in 0..", nodecl.} 10 | 11 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 12 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 13 | let cLen = len div sizeof(float) 14 | if cLen == 0: return 15 | var copy = newSeq[float](cLen) 16 | copyMem(addr copy[0], data, copy.len * sizeof(float)) 17 | 18 | let res = sum(copy) 19 | if isNaN(res): 20 | quitOrDebug() 21 | 22 | proc customMutator(data: ptr UncheckedArray[byte], len, maxLen: int, seed: int64): int {. 23 | exportc: "LLVMFuzzerCustomMutator", raises: [].} = 24 | 25 | let cLen = len div sizeof(float) 26 | if cLen == 0: 27 | var tmp = @[1.0, 3, 3, 7] # Use a dummy value 28 | result = tmp.len * sizeof(float) 29 | copyMem(data, addr tmp[0], result) 30 | return 31 | var copy = newSeq[float](cLen) 32 | copyMem(addr copy[0], data, copy.len * sizeof(float)) 33 | var gen = initRand(seed) 34 | 35 | proc rfp(gen: var Rand): float = 36 | case gen.rand(10) 37 | of 0: 38 | result = NaN 39 | of 1: 40 | result = minimumPositiveValue(float) 41 | of 2: 42 | result = maximumPositiveValue(float) 43 | of 3: 44 | result = -minimumPositiveValue(float) 45 | of 4: 46 | result = -maximumPositiveValue(float) 47 | of 5: 48 | result = epsilon(float) 49 | of 6: 50 | result = -epsilon(float) 51 | of 7: 52 | result = Inf 53 | of 8: 54 | result = -Inf 55 | of 9: 56 | result = 0.0 57 | else: 58 | result = gen.rand(-1.0..1.0) 59 | 60 | case gen.rand(3) 61 | of 0: # Change element 62 | if copy.len > 0: 63 | copy[gen.rand(0.. 0: 68 | discard copy.pop 69 | else: # Shuffle elements 70 | gen.shuffle(copy) 71 | 72 | result = copy.len * sizeof(float) 73 | if result <= maxLen: 74 | copyMem(data, addr copy[0], result) 75 | else: 76 | result = 0 77 | 78 | proc customCrossOver(data1: ptr UncheckedArray[byte], len1: int, 79 | data2: ptr UncheckedArray[byte], len2: int, res: ptr UncheckedArray[byte], 80 | maxResLen: int, seed: int64): int {. 81 | exportc: "LLVMFuzzerCustomCrossOver", raises: [].} = 82 | 83 | let cLen1 = len1 div sizeof(float) 84 | if cLen1 == 0: return 85 | var copy1 = newSeq[float](cLen1) 86 | copyMem(addr copy1[0], data1, copy1.len * sizeof(float)) 87 | 88 | let cLen2 = len2 div sizeof(float) 89 | if cLen2 == 0: return 90 | var copy2 = newSeq[float](cLen2) 91 | copyMem(addr copy2[0], data2, copy2.len * sizeof(float)) 92 | 93 | let len = min(copy1.len, min(copy2.len, maxResLen div sizeof(float))) 94 | if len == 0: return 95 | var buf = newSeq[float](len) 96 | copyMem(addr buf[0], res, buf.len * sizeof(float)) 97 | 98 | var gen = initRand(seed) 99 | for i in 0 ..< buf.len: 100 | buf[i] = if gen.rand(1.0) <= 0.5: copy1[i] else: copy2[i] 101 | 102 | result = buf.len * sizeof(float) 103 | assert result <= maxResLen 104 | copyMem(res, addr buf[0], result) 105 | -------------------------------------------------------------------------------- /examples/imgread.nim: -------------------------------------------------------------------------------- 1 | # https://www.youtube.com/watch?v=hFva8kJQwnc 2 | # a vulnerable C program to explain common vulnerability types fuzz with libfuzzer 3 | # ./imgread -fork=1 -ignore_crashes=1 4 | 5 | type 6 | Image = object 7 | header: array[4, char] 8 | width, height: cint 9 | data: array[10, char] 10 | 11 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 12 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 13 | if len < sizeof(Image): 14 | return 0 15 | let img = cast[ptr Image](data) 16 | # integer overflow 0x7FFFFFFF+1=0 17 | # 0x7FFFFFFF+2 = 1 18 | # will cause very large/small memory allocation. 19 | let size1 = img.width + img.height 20 | var buff1 = cast[cstring](alloc(size1)) 21 | # heap buffer overflow 22 | copyMem(buff1, addr img.data, sizeof(img.data)) 23 | dealloc(buff1) 24 | # double dealloc 25 | if size1 div 3 == 0: 26 | dealloc(buff1) 27 | else: 28 | # use after dealloc 29 | if size1 div 20 == 0: 30 | buff1[0] = 'a' 31 | # integer underflow 0-1=-1 32 | # negative so will cause very large memory allocation 33 | let size2 = img.width - img.height + 100 34 | # echo("Size1: ", size1) 35 | let buff2 = cast[cstring](alloc(size2)) 36 | # heap buffer overflow 37 | copyMem(buff2, addr img.data, sizeof(img.data)) 38 | # divide by zero 39 | let size3 = img.width div img.height 40 | # echo("Size2: ", size3) 41 | var buff3: array[10, char] 42 | var buff4 = cast[cstring](alloc(size3)) 43 | copyMem(buff4, addr img.data, sizeof(img.data)) 44 | # stack OOBR read bytes past buffer 45 | let OOBR_stack = buff3[size3] 46 | let OOBR_heap = buff4[size1] 47 | # stack OOBW write bytes past buffer 48 | buff3[size3] = 'c' 49 | buff4[size1] = 'c' 50 | if size3 div 5 == 0: 51 | # memory leak here 52 | buff4 = nil 53 | else: 54 | dealloc(buff4) 55 | dealloc(buff2) 56 | -------------------------------------------------------------------------------- /experiments/altapi.nim: -------------------------------------------------------------------------------- 1 | # Alternative to strict .raises: [] 2 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 3 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 4 | 5 | template trap(body: untyped) = 6 | try: 7 | body 8 | finally: {.emit: "nimTestErrorFlag();".} 9 | 10 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 11 | exportc: "LLVMFuzzerTestOneInput", trap.} = 12 | if true: 13 | raise newException(ValueError, "my my my") 14 | -------------------------------------------------------------------------------- /experiments/api_exp.nim: -------------------------------------------------------------------------------- 1 | proc LLVMFuzzerMutate(data: ptr UncheckedArray[byte], len, maxLen: int): int {. 2 | importc.} 3 | proc mutate(data: var openarray[byte], oldLen: int): int {.inline.} = 4 | LLVMFuzzerMutate(cast[ptr UncheckedArray[byte]](data), oldLen, data.len) 5 | proc customMutator(data: var openarray[byte], oldLen: int, seed: int64): int {. 6 | exportc: "LLVMFuzzerCustomMutator".} = discard "to implement" 7 | proc LLVMFuzzerCustomMutator(data: ptr UncheckedArray[byte], len, maxLen: int, seed: int64): int {. 8 | exportc.} = customMutator(toOpenArray(data, 0, maxLen-1), len) 9 | #Error: type mismatch: got 10 | #but expected one of: 11 | #proc customMutator(data: var openArray[byte]; oldLen: int; seed: int64): int 12 | #first type mismatch at position: 1 13 | #required type for data: var openArray[byte] 14 | #but expression 'toOpenArray(data, 0, maxLen - 1)' is immutable, not 'var' 15 | 16 | #expression: customMutator(toOpenArray(data, 0, maxLen - 1), len) 17 | -------------------------------------------------------------------------------- /experiments/memstreams.nim: -------------------------------------------------------------------------------- 1 | import std/streams 2 | 3 | type 4 | MemStream* = ref MemStreamObj 5 | ## A stream that encapsulates a openarray[byte]. 6 | MemStreamObj = object of StreamObj 7 | data: ptr UncheckedArray[byte] 8 | len, pos: int 9 | 10 | proc `=sink`*(dest: var MemStreamObj; source: MemStreamObj) {.error.} 11 | proc `=copy`*(dest: var MemStreamObj; source: MemStreamObj) {.error.} 12 | 13 | proc msAtEnd(s: Stream): bool = 14 | let s = MemStream(s) 15 | result = s.pos >= s.len 16 | 17 | proc msSetPosition(s: Stream, pos: int) = 18 | let s = MemStream(s) 19 | s.pos = clamp(pos, 0, s.len) 20 | 21 | proc msGetPosition(s: Stream): int = 22 | let s = MemStream(s) 23 | result = s.pos 24 | 25 | proc msReadData(s: Stream, buffer: pointer, bufLen: int): int = 26 | let s = MemStream(s) 27 | result = min(bufLen, s.len - s.pos) 28 | if result > 0: 29 | copyMem(buffer, addr s.data[s.pos], result) 30 | inc(s.pos, result) 31 | else: 32 | result = 0 33 | 34 | proc msPeekData(s: Stream, buffer: pointer, bufLen: int): int = 35 | let s = MemStream(s) 36 | result = min(bufLen, s.len - s.pos) 37 | if result > 0: 38 | copyMem(buffer, addr s.data[s.pos], result) 39 | else: 40 | result = 0 41 | 42 | proc msWriteData(s: Stream, buffer: pointer, bufLen: int) = 43 | var s = MemStream(s) 44 | if bufLen <= 0: 45 | return 46 | if s.pos + bufLen > s.len: 47 | raise newException(IOError, "cannot write to stream") 48 | copyMem(addr(s.data[s.pos]), buffer, bufLen) 49 | inc(s.pos, bufLen) 50 | 51 | proc newMemStream*(s: openarray[byte]): MemStream = 52 | result = MemStream( 53 | data: cast[ptr UncheckedArray[byte]](s), 54 | len: s.len, 55 | pos: 0, 56 | closeImpl: nil, 57 | atEndImpl: msAtEnd, 58 | setPositionImpl: msSetPosition, 59 | getPositionImpl: msGetPosition, 60 | readDataStrImpl: nil, 61 | readDataImpl: msReadData, 62 | peekDataImpl: msPeekData, 63 | writeDataImpl: msWriteData 64 | ) 65 | 66 | proc newMemStream*(data: ptr UncheckedArray[byte], len: int): MemStream = 67 | result = MemStream( 68 | data: data, 69 | len: len, 70 | pos: 0, 71 | closeImpl: nil, 72 | atEndImpl: msAtEnd, 73 | setPositionImpl: msSetPosition, 74 | getPositionImpl: msGetPosition, 75 | readDataStrImpl: nil, 76 | readDataImpl: msReadData, 77 | peekDataImpl: msPeekData, 78 | writeDataImpl: msWriteData 79 | ) 80 | -------------------------------------------------------------------------------- /experiments/nfpsum.nim: -------------------------------------------------------------------------------- 1 | # https://rigtorp.se/fuzzing-floating-point-code/ 2 | import std/[random, fenv, math, streams], bingo, memstreams 3 | 4 | proc sum(x: openArray[float]): float = 5 | result = 0.0 6 | for b in items(x): 7 | result = if isNaN(b): result else: result + b 8 | 9 | proc quitOrDebug() {.noreturn, importc: "abort", header: "", nodecl.} 10 | 11 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 12 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 13 | var copy: seq[float] 14 | try: 15 | let str = newMemStream(data, len) 16 | loadBin(str, copy) 17 | except: 18 | return 19 | if copy.len == 0: return 20 | let res = sum(copy) 21 | if isNaN(res): 22 | quitOrDebug() 23 | 24 | when defined(fuzzSa): 25 | include libfuzzer/standalone 26 | else: 27 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 28 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 29 | 30 | proc customMutator(data: ptr UncheckedArray[byte], len, maxLen: int, seed: int64): int {. 31 | exportc: "LLVMFuzzerCustomMutator".} = 32 | 33 | proc randFloat(gen: var Rand): float = 34 | case gen.rand(10) 35 | of 0: 36 | result = NaN 37 | of 1: 38 | result = minimumPositiveValue(float) 39 | of 2: 40 | result = maximumPositiveValue(float) 41 | of 3: 42 | result = -minimumPositiveValue(float) 43 | of 4: 44 | result = -maximumPositiveValue(float) 45 | of 5: 46 | result = epsilon(float) 47 | of 6: 48 | result = -epsilon(float) 49 | of 7: 50 | result = Inf 51 | of 8: 52 | result = -Inf 53 | of 9: 54 | result = 0.0 55 | else: 56 | result = gen.rand(-1.0..1.0) 57 | 58 | var copy: seq[float] 59 | try: 60 | let readStr = newMemStream(data, len) 61 | loadBin(readStr, copy) 62 | except: 63 | let writeStr = newMemStream(data, maxLen) 64 | writeStr.storeBin(@[1.0, 2, 3, 4]) 65 | result = writeStr.getPosition() 66 | 67 | if copy.len == 0: return 68 | var gen = initRand(seed) 69 | case gen.rand(3) 70 | of 0: # Change element 71 | if copy.len > 0: 72 | copy[gen.rand(0.. 0: 77 | discard copy.pop 78 | else: # Shuffle elements 79 | gen.shuffle(copy) 80 | 81 | result = copy.byteSize 82 | if result <= maxLen: 83 | let writeStr = newMemStream(data, maxLen) 84 | writeStr.storeBin(copy) 85 | else: 86 | result = len 87 | 88 | proc customCrossOver(data1: ptr UncheckedArray[byte], len1: int, 89 | data2: ptr UncheckedArray[byte], len2: int, res: ptr UncheckedArray[byte], 90 | maxResLen: int, seed: int64): int {. 91 | exportc: "LLVMFuzzerCustomCrossOver".} = 92 | 93 | var copy1: seq[float] 94 | try: 95 | let readStr1 = newMemStream(data1, len1) 96 | loadBin(readStr1, copy1) 97 | except: 98 | return 99 | 100 | var copy2: seq[float] 101 | try: 102 | let readStr2 = newMemStream(data2, len2) 103 | loadBin(readStr2, copy2) 104 | except: 105 | return 106 | 107 | let len = min(copy1.len, min(copy2.len, maxResLen div sizeof(float))) 108 | if len == 0: return 109 | var buf = newSeq[float](len) 110 | 111 | var gen = initRand(seed) 112 | for i in 0 ..< buf.len: 113 | buf[i] = if gen.rand(1.0) <= 0.5: copy1[i] 114 | else: copy2[i] 115 | 116 | result = buf.byteSize 117 | if result <= maxResLen: 118 | let writeStr = newMemStream(res, maxResLen) 119 | writeStr.storeBin(buf) 120 | else: 121 | result = len 122 | -------------------------------------------------------------------------------- /experiments/nfpsum.nims: -------------------------------------------------------------------------------- 1 | --cc: clang 2 | --debugger: native # ignored 3 | #--header 4 | --define: noSignalHandler 5 | --define: useMalloc 6 | when not defined(fuzzSa): 7 | --noMain: on 8 | --passC: "-fsanitize=fuzzer" 9 | --passL: "-fsanitize=fuzzer" 10 | --passC: "-fsanitize=address,undefined" 11 | --passL: "-fsanitize=address,undefined" 12 | --path: "../" 13 | -------------------------------------------------------------------------------- /experiments/tstructure.nim: -------------------------------------------------------------------------------- 1 | import std/streams, bingo, memstreams, chroma 2 | 3 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 4 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 5 | 6 | proc fuzzTarget(color: ColorRgb) = 7 | let hsl = color.asHsl 8 | let rgb = hsl.asRgb 9 | # This should be true for all RGB -> HSL -> RGB conversions! 10 | doAssert color == rgb 11 | 12 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 13 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 14 | var target: ColorRgb 15 | try: 16 | let str = newReadStream(data, len) 17 | loadBin(str, target) 18 | except: 19 | return 20 | fuzzTarget(target) 21 | -------------------------------------------------------------------------------- /libfuzzer.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Antonis Geralis" 5 | description = "Thin interface for libFuzzer, an in-process, coverage-guided, evolutionary fuzzing engine." 6 | license = "MIT" 7 | 8 | # Deps 9 | 10 | requires "nim >= 1.0.0" 11 | 12 | import os 13 | 14 | const 15 | ProjectUrl = "https://github.com/planetis-m/libfuzzer" 16 | PkgDir = thisDir().quoteShell 17 | DocsDir = PkgDir / "docs" 18 | 19 | task docs, "Generate documentation": 20 | # https://nim-lang.github.io/Nim/docgen.html 21 | withDir(PkgDir): 22 | let tmp = "fuzztarget" 23 | let doc = DocsDir / (tmp & ".html") 24 | let src = "libfuzzer" / (tmp & ".nim") 25 | # Generate the docs for {src} 26 | exec("nim doc --verbosity:0 --git.url:" & ProjectUrl & 27 | " --git.devel:master --git.commit:master --out:" & doc & " " & src) 28 | -------------------------------------------------------------------------------- /libfuzzer/fuzztarget.nim: -------------------------------------------------------------------------------- 1 | ## NOTE: the libFuzzer interface is thin and in the majority of cases 2 | ## all you need is to define the procedure `testOneInput` in your file. 3 | 4 | proc testOneInput*(data: ptr UncheckedArray[byte], len: int): cint {. 5 | exportc: "LLVMFuzzerTestOneInput".} = 6 | ## Mandatory user-provided target procedure. 7 | ## Executes the code under test with `data` as the input. 8 | ## libFuzzer will invoke this procedure *many* times with different inputs. 9 | ## Must return 0. 10 | discard "to implement" 11 | 12 | when defined(fuzzSa) or defined(nimdoc): 13 | include standalone 14 | when not defined(fuzzSa) or defined(nimdoc): 15 | proc initialize*(): cint {.exportc: "LLVMFuzzerInitialize".} = 16 | ## Initialize Nim's internals, which is done calling a NimMain function. 17 | # https://nim-lang.github.io/Nim/backends.html#interfacing-backend-code-calling-nim 18 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 19 | 20 | proc mutate*(data: ptr UncheckedArray[byte], len, maxLen: int): int {. 21 | importc: "LLVMFuzzerMutate".} 22 | ## Experimental, may go away in future. 23 | ## libFuzzer-provided procedure to be used inside `customMutator`. 24 | ## Mutates raw data in `data[0..`_ 19 | 20 | Clang Sanitizers 21 | ================ 22 | 23 | Sanitizers are compiler build-in error detectors with relatively small runtime 24 | cost. Clang has: 25 | 26 | - `AddressSanitizer `_ - use-after-free, double-free, ... 27 | - `MemorySanitizer `_ - uninitialized reads 28 | - `UndefinedBehaviourSanitizer `_ - overflows, divide by zero, ... 29 | - `ThreadSanitizer `_ - data races 30 | 31 | For more information watch the talk *Sanitize your C++ code* [4]_ 32 | There are demos at the `tests `_ directory. 33 | 34 | Example 35 | ======= 36 | 37 | In 95% of cases all you need is to define the procedure ``testOneInput`` in your file. 38 | 39 | 40 | .. code-block:: nim 41 | 42 | proc fuzzMe(data: openarray[byte]): bool = 43 | result = data.len >= 3 and 44 | data[0].char == 'F' and 45 | data[1].char == 'U' and 46 | data[2].char == 'Z' and 47 | data[3].char == 'Z' # :‑< 48 | 49 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 50 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 51 | 52 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 53 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 54 | result = 0 55 | discard fuzzMe(data.toOpenArray(0, len-1)) 56 | 57 | 58 | Compile with: 59 | 60 | .. code-block:: 61 | 62 | $ nim c --cc:clang -t:"-fsanitize=fuzzer,address,undefined" -l:"-fsanitize=fuzzer,address,undefined" -d:nosignalhandler --nomain:on -g tfuzz.nim 63 | 64 | 65 | Coverage report 66 | =============== 67 | 68 | Use `Clang Coverage `_ to visualize and study your code coverage. 69 | 70 | - Include the `standalone `_ main procedure for fuzz targets. 71 | - Follow the instructions given at the `test coverage `_ example. 72 | - When running the executable, pass as parameter a list of test units. 73 | 74 | Structure-Aware Fuzzing 75 | ======================= 76 | 77 | But the lack of an input grammar can also result in inefficient fuzzing 78 | for complicated input types, where any traditional mutation (e.g. bit 79 | flipping) leads to an invalid input rejected by the target API in the 80 | early stage of parsing. With some additional effort, however, libFuzzer 81 | can be turned into a grammar-aware (i.e. structure-aware) fuzzing engine 82 | for a specific input type. 83 | 84 | —*Structure-Aware Fuzzing with libFuzzer* [5]_ 85 | 86 | Take a look at the snappy compression `example `_ 87 | and ` `_ 88 | 89 | Installation 90 | ============ 91 | 92 | - Copy the files ``libfuzzer/fuzztarget.{nim,nims}``, ``libfuzzer/standalone.nim`` at your testing directory. 93 | - Fill in the implementations of the exported procedures. 94 | - Compile and run with an empty corpus directory as an argument. 95 | 96 | Presentations 97 | ============= 98 | 99 | .. [#] Jonathan Metzman `Fuzzing 101 `_ 100 | .. [#] Kostya Serebryany `Fuzz or lose... `_ 101 | .. [#] Kostya Serebryany `Sanitize your C++ code `_ 102 | 103 | Further Readings 104 | ================ 105 | 106 | .. [#] `libFuzzer Tutorial `_ 107 | .. [#] `Structure-Aware Fuzzing with libFuzzer `_ 108 | .. [#] `Efficient Fuzzing Guide `_ 109 | -------------------------------------------------------------------------------- /tests/shadowmem.nim: -------------------------------------------------------------------------------- 1 | import std/strformat 2 | 3 | {.pragma: noASan, codegenDecl: "__attribute__((no_sanitize_address)) $# $#$#".} 4 | 5 | proc getShadowMapping(shadowScale, shadowOffset: ptr int) {.header: 6 | "sanitizer/asan_interface.h", importc: "__asan_get_shadow_mapping".} 7 | 8 | var 9 | shadowMemoryScale: int 10 | shadowMemoryOffset: int 11 | 12 | getShadowMapping(addr shadowMemoryScale, addr shadowMemoryOffset) 13 | 14 | proc printShadowMemoryImpl(address: pointer, filename: string, line: int) {.noASan.} = 15 | let shadowMemory = cast[ptr UncheckedArray[uint8]]( 16 | cast[uint](address) shr shadowMemoryScale + shadowMemoryOffset.uint) 17 | stdout.write(&"Shadow Memory at {filename}:{line}\n") 18 | stdout.write(&"{cast[ByteAddress](address):#x}: {shadowMemory[0]:02x} {shadowMemory[1]:02x} {shadowMemory[2]:02x} {shadowMemory[3]:02x} {shadowMemory[4]:02x} {shadowMemory[5]:02x} {shadowMemory[6]:02x} {shadowMemory[7]:02x}\n") 19 | 20 | template printShadowMemory*(address: untyped) = 21 | let (filename, line, _) = instantiationInfo() 22 | printShadowMemoryImpl(address, filename, line) 23 | -------------------------------------------------------------------------------- /tests/tasan.nim: -------------------------------------------------------------------------------- 1 | # --panics:on --gc:arc -d:useMalloc -t:"-fsanitize=address,undefined" 2 | # -l:"-fsanitize=address,undefined" -d:nosignalhandler -d:danger -g 3 | 4 | import os, strutils, algorithm 5 | 6 | var 7 | data: array[1000, int] 8 | 9 | proc main: int = 10 | fill data, -1 11 | let idx = parseInt(paramStr(1)) 12 | result = data[idx + 100] 13 | 14 | discard main() 15 | 16 | #[ 17 | const 18 | arrLen = 1_000 19 | 20 | type 21 | Array = object 22 | data: ptr array[arrLen, int] 23 | 24 | proc `=destroy`*(x: var Array) = 25 | if x.data != nil: 26 | dealloc(x.data) 27 | 28 | proc init(x: var Array) = 29 | x.data = cast[typeof(x.data)](alloc(arrLen * sizeof(int))) 30 | 31 | proc main = 32 | var 33 | arr: Array 34 | init arr 35 | `=destroy`(arr) 36 | echo arr.data[0] 37 | 38 | main()]# 39 | -------------------------------------------------------------------------------- /tests/tcmdline.nim: -------------------------------------------------------------------------------- 1 | # os.paramCount/paramStr not supported 2 | import std/parseopt 3 | 4 | proc quitOrDebug() {.noreturn, importc: "abort", header: "", nodecl.} 5 | 6 | var 7 | cmdCountPtr: ptr cint 8 | cmdLinePtr: ptr cstringArray 9 | 10 | proc initialize(argc: ptr cint; argv: ptr cstringArray): cint {.exportc: "LLVMFuzzerInitialize".} = 11 | cmdCountPtr = argc 12 | cmdLinePtr = argv 13 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 14 | 15 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 16 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 17 | let cmdline = cstringArrayToSeq(cmdLinePtr[], cmdCountPtr[]) 18 | var p = initOptParser(cmdline) 19 | for kind, key, val in p.getopt(): 20 | case kind 21 | of cmdLongOption, cmdShortOption: 22 | case key 23 | of "abort", "a": quitOrDebug() 24 | else: discard 25 | -------------------------------------------------------------------------------- /tests/tcov.nim: -------------------------------------------------------------------------------- 1 | # Compile with the following flags 2 | # For explanation: https://nim-lang.github.io/Nim/nimc.html 3 | # --panics:on --gc:arc -d:useMalloc --cc:clang -t:"-fprofile-instr-generate -fcoverage-mapping" 4 | # -l:"-fprofile-instr-generate -fcoverage-mapping" -d:danger 5 | # Run the executable 6 | # llvm-profdata merge -sparse=true default.profraw -o default.profdata 7 | # llvm-cov show -instr-profile=default.profdata -name=foo_tcov ./tcov 8 | # Missing: 9 | # Output Nim source code 10 | # Specify a Nim demangler with -Xdemangler 11 | template bar(x: untyped): untyped = x or x 12 | 13 | proc foo[T](x: T) = 14 | for i in 0..<10: 15 | discard bar i 16 | 17 | proc main = 18 | foo[int32](0) 19 | foo[float32](0) 20 | 21 | main() 22 | -------------------------------------------------------------------------------- /tests/tcrossover.nim: -------------------------------------------------------------------------------- 1 | import std/[hashes, strformat] 2 | 3 | const 4 | Separator = "-########-" 5 | Target = "A-########-B" 6 | 7 | var 8 | sink: int 9 | printed: int 10 | 11 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 12 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 13 | 14 | proc testOneInput*(data: ptr UncheckedArray[byte], len: int): cint {. 15 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 16 | result = 0 17 | const targetHash = hash(Target) 18 | var strHash = hash(data.toOpenArray(0, len-1)) 19 | # Ensure we have 'A' and 'B' in the corpus. 20 | if len == 1 and data[0] == byte'A': 21 | inc(sink) 22 | if len == 1 and data[0] == byte'B': 23 | dec(sink) 24 | if targetHash == strHash: 25 | quit "BINGO; Found the target, exiting" 26 | 27 | proc customCrossOver(data1: ptr UncheckedArray[byte], len1: int, 28 | data2: ptr UncheckedArray[byte], len2: int, res: ptr UncheckedArray[byte], 29 | maxResLen: int, seed: int64): int {. 30 | exportc: "LLVMFuzzerCustomCrossOver".} = 31 | const separatorLen = len(Separator) 32 | if printed < 32: 33 | stderr.write &"In customCrossover {len1} {len2}\n" 34 | inc(printed) 35 | result = len1 + len2 + separatorLen 36 | if result > maxResLen: 37 | return 0 38 | for i in 0..", nodecl.} 2 | 3 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 4 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 5 | 6 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 7 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 8 | if true: 9 | raise newException(RangeDefect, "my my my") 10 | 11 | proc customMutator(data: ptr UncheckedArray[byte]; len, maxLen: int, seed: int64): int {. 12 | exportc: "LLVMFuzzerCustomMutator".} = 13 | try: 14 | discard 15 | except: 16 | echo getCurrentExceptionMsg() 17 | quitOrDebug() 18 | -------------------------------------------------------------------------------- /tests/tfuzz.nim: -------------------------------------------------------------------------------- 1 | # https://llvm.org/docs/LibFuzzer.html 2 | 3 | # --panics:on --gc:arc -d:useMalloc --cc:clang -t:"-fsanitize=fuzzer,address,undefined" 4 | # -l:"-fsanitize=fuzzer,address,undefined" -d:nosignalhandler --nomain:on -d:danger -g 5 | 6 | # objdump --no-show-raw-insn -drwlS -M intel --start-address=0x 7 | 8 | proc fuzzMe(data: openarray[byte]): bool = 9 | result = data.len >= 3 and 10 | data[0].char == 'F' and 11 | data[1].char == 'U' and 12 | data[2].char == 'Z' and 13 | data[3].char == 'Z' # :‑< 14 | 15 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 16 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 17 | 18 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 19 | exportc: "LLVMFuzzerTestOneInput", raises: [].} = 20 | result = 0 21 | discard fuzzMe(data.toOpenArray(0, len-1)) 22 | -------------------------------------------------------------------------------- /tests/tmem.nim: -------------------------------------------------------------------------------- 1 | # Contrived example of ASan poisoning, use `-d:useMalloc` 2 | # https://github.com/mcgov/asan_alignment_example 3 | import shadowmem 4 | 5 | proc poisonMem(region: pointer, size: int) {.header: 6 | "sanitizer/asan_interface.h", importc: "ASAN_POISON_MEMORY_REGION".} 7 | 8 | proc unpoisonMem(region: pointer, size: int) {.header: 9 | "sanitizer/asan_interface.h", importc: "ASAN_UNPOISON_MEMORY_REGION".} 10 | 11 | template `+!`(p: pointer, s: int): pointer = 12 | cast[pointer](cast[int](p) +% s) 13 | 14 | type 15 | Point = object 16 | x: float32 17 | 18 | var 19 | points = newSeq[Point](5) 20 | # Print the address 21 | printShadowMemory(addr points[1]) 22 | # Poison the entire seq 23 | poisonMem(addr points[0], points.len * sizeof(Point)) 24 | # Create a Point 25 | unpoisonMem(addr points[1], sizeof(Point)) 26 | points[1] = Point(x: 1) 27 | echo points[1] 28 | # Pretend to destroy `points[1]` 29 | poisonMem(addr points[1], sizeof(Point)) 30 | printShadowMemory(addr points[1]) 31 | echo points[1] 32 | -------------------------------------------------------------------------------- /tests/traise.nim: -------------------------------------------------------------------------------- 1 | proc quitOrDebug() {.noreturn, importc: "abort", header: "", nodecl.} 2 | 3 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 4 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 5 | 6 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 7 | exportc: "LLVMFuzzerTestOneInput".} = #, raises: [].} = 8 | if true: 9 | raise newException(ValueError, "my my my") 10 | 11 | proc customMutator(data: ptr UncheckedArray[byte]; len, maxLen: int, seed: int64): int {. 12 | exportc: "LLVMFuzzerCustomMutator".} = 13 | try: 14 | discard 15 | except: 16 | echo getCurrentExceptionMsg() 17 | quitOrDebug() 18 | -------------------------------------------------------------------------------- /tests/tstring.nim: -------------------------------------------------------------------------------- 1 | # https://www.moritz.systems/blog/an-introduction-to-llvm-libfuzzer/ 2 | # Compile with and without asan builtin interceptors "-fsanitize=fuzzer(,address)" 3 | # ./tstring -runs=1000000 4 | 5 | proc quitOrDebug() {.noreturn, importc: "abort", header: "", nodecl.} 6 | 7 | proc initialize(): cint {.exportc: "LLVMFuzzerInitialize".} = 8 | {.emit: "N_CDECL(void, NimMain)(void); NimMain();".} 9 | 10 | proc testOneInput(data: ptr UncheckedArray[byte], len: int): cint {. 11 | exportc: "LLVMFuzzerTestOneInput".} = 12 | if len >= 7: 13 | var copy = newString(6) 14 | copyMem(cstring(copy), cast[cstring](data), copy.len) 15 | if copy == "qwerty": 16 | stderr.write("BINGO\n") 17 | quitOrDebug() 18 | -------------------------------------------------------------------------------- /tests/ttsan.nim: -------------------------------------------------------------------------------- 1 | # https://github.com/google/sanitizers/wiki/ThreadSanitizerPopularDataRaces 2 | # pbl benign 3 | # --threads:on --panics:on --gc:arc -d:useMalloc -t:"-fsanitize=thread" 4 | # -l:"-fsanitize=thread" -d:nosignalhandler -d:danger -g 5 | # TSAN_OPTIONS="force_seq_cst_atomics=1" 6 | import std/[atomics, os] 7 | 8 | const 9 | delay = 1_000 10 | 11 | var 12 | thread: Thread[void] 13 | proceed: Atomic[bool] 14 | bArrived = false 15 | 16 | proc routine = 17 | var count = 0 18 | while true: 19 | if count mod delay == 0 and proceed.load(moRelaxed): 20 | break 21 | cpuRelax() 22 | inc count 23 | doAssert bArrived 24 | 25 | proc testNotify = 26 | createThread(thread, routine) 27 | sleep 10 28 | bArrived = true 29 | proceed.store(true, moRelaxed) 30 | joinThread thread 31 | 32 | testNotify() 33 | --------------------------------------------------------------------------------