8000 Merge pull request #1 from ennamarie19/mayhem · tableau/document-api-python@4faee6a · GitHub
[go: up one dir, main page]

Skip to content

Commit 4faee6a

Browse files
authored
Merge pull request #1 from ennamarie19/mayhem
Mayhem Heroes Integration
2 parents a2f178c + 66fd405 commit 4faee6a

File tree

6 files changed

+270
-0
lines changed

6 files changed

+270
-0
lines changed

.github/workflows/mayhem.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Mayhem
2+
on:
3+
push:
4+
pull_request:
5+
workflow_dispatch:
6+
7+
env:
8+
REGISTRY: ghcr.io
9+
IMAGE_NAME: ${{ github.repository }}
10+
11+
jobs:
12+
build:
13+
name: 'Build mayhem fuzzing container'
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v2
17+
18+
- name: Log in to the Container registry
19+
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
20+
with:
21+
registry: ${{ env.REGISTRY }}
22+
username: ${{ github.actor }}
23+
password: ${{ secrets.GITHUB_TOKEN }}
24+
25+
- name: Extract metadata (tags, labels) for Docker
26+
id: meta
27+
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
28+
with:
29+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
30+
31+
- name: Build and push Docker image
32+
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
33+
with:
34+
context: .
35+
file: mayhem/Dockerfile
36+
push: true
37+
tags: ${{ steps.meta.outputs.tags }}
38+
labels: ${{ steps.meta.outputs.labels }}
39+
40+
outputs:
41+
image: ${{ steps.meta.outputs.tags }}
42+
43+
mayhem:
44+
needs: build
45+
name: 'fuzz ${{ matrix.mayhemfile }}'
46+
runs-on: ubuntu-latest
47+
strategy:
48+
fail-fast: false
49+
matrix:
50+
mayhemfile:
51+
- mayhem/Mayhemfile
52+
53+
steps:
54+
- uses: actions/checkout@v2
55+
56+
- name: Start analysis for ${{ matrix.mayhemfile }}
57+
uses: ForAllSecure/mcode-action@v1
58+
with:
59+
mayhem-token: ${{ secrets.MAYHEM_TOKEN }}
60+
args: --image ${{ needs.build.outputs.image }} --file ${{ matrix.mayhemfile }} --duration 300
61+
sarif-output: sarif
62+

mayhem/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Build Stage
2+
FROM fuzzers/atheris:2.0.7-python3.9
3+
4+
ADD . /src
5+
WORKDIR /src
6+
RUN python3 -m pip install .
7+
8+
CMD ["/src/mayhem/fuzz_twb.py"]

mayhem/Mayhemfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
project: document-api-python
2+
target: fuzz-twb
3+
testsuite:
4+
- file://mayhem/testsuite
5+
6+
cmds:
7+
- cmd: /src/mayhem/fuzz_twb.py
8+
libfuzzer: true
9+
timeout: 15

mayhem/fuzz_helpers.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Atheris fuzzing utilities written by Bailey Capuano
2+
import io
3+
import tempfile
4+
import atheris
5+
import contextlib
6+
from typing import List, Set, Dict, Tuple, Any
7+
8+
9+
def _handle_type(fdp: atheris.FuzzedDataProvider, ty_queue: List[type]) -> Any:
10+
"""
11+
Handles the fuzzing of a single type.
12+
:param fdp: FuzzedDataProvider object
13+
:param ty_queue: The current stack of types to be used for fuzzing
14+
:return: The fuzzed element
15+
"""
16+
if not ty_queue:
17+
return None
18+
ty = ty_queue.pop(0)
19+
if ty is bytes:
20+
return fdp.ConsumeBytes(fdp.ConsumeIntInRange(0, 100))
21+
elif ty is bytearray:
22+
return bytearray(fdp.ConsumeBytes(fdp.ConsumeIntInRange(0, 100)))
23+
elif ty is str:
24+
return fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 100))
25+
elif ty is float:
26+
return fdp.ConsumeRegularFloat()
27+
elif ty is bool:
28+
return fdp.ConsumeBool()
29+
elif ty is int:
30+
return fdp.ConsumeInt(4)
31+
elif ty is dict:
32+
return build_fuzz_dict(fdp, ty_queue)
33+
elif ty is list:
34+
return build_fuzz_list(fdp, ty_queue)
35+
elif ty is set:
36+
return build_fuzz_set(fdp, ty_queue)
37+
elif ty is tuple:
38+
return build_fuzz_tuple(fdp, ty_queue)
39+
else:
40+
return None
41+
42+
43+
def build_fuzz_list(fdp: atheris.FuzzedDataProvider, ty_queue: List[type]) -> List[Any]:
44+
"""
45+
Builds a list with fuzzer-defined elements.
46+
:param fdp: FuzzedDataProvider object
47+
:param ty_queue: The current stack of types to be used for fuzzing
48+
:return: The list
49+
"""
50+
if not ty_queue:
51+
return []
52+
elem_count = fdp.ConsumeIntInRange(1, 5)
53+
gen_list = []
54+
55+
for _ in range(elem_count):
56+
passed_queue = ty_queue.copy()
57+
elem = _handle_type(fdp, passed_queue)
58+
if elem is not None:
59+
gen_list.append(elem)
60+
ty_queue.pop(0) # Pop elem type
61+
62+
return gen_list
63+
64+
65+
def build_fuzz_set(fdp: atheris.FuzzedDataProvider, ty_queue: List[type]) -> Set[Any]:
66+
"""
67+
Builds a set with fuzzer-defined elements.
68+
:param fdp: FuzzedDataProvider object
69+
:param ty_queue: The current stack of types to be used for fuzzing
70+
:return: The set
71+
"""
72+
if not ty_queue:
73+
return set()
74+
ty_queue.insert(0, list)
75+
76+
fuzz_list = _handle_type(fdp, ty_queue)
77+
return set(fuzz_list)
78+
79+
80+
def build_fuzz_tuple(fdp: atheris.FuzzedDataProvider, ty_queue: List[type]) -> Tuple[Any]:
81+
"""
82+
Builds a tuple with fuzzer-defined elements.
83+
:param fdp: FuzzedDataProvider object
84+
:param ty_queue: The current stack of types to be used for fuzzing
85+
:return: The tuple
86+
"""
87+
if not ty_queue:
88+
return tuple()
89+
ty_queue.insert(0, list)
90+
91+
fuzz_list = _handle_type(fdp, ty_queue)
92+
return tuple(fuzz_list)
93+
94+
95+
def build_fuzz_dict(fdp: atheris.FuzzedDataProvider, ty_queue: List[type]) -> Dict[Any, Any]:
96+
"""
97+
Builds a dictionary with fuzzer-defined keys and values.
98+
:param fdp: FuzzedDataProvider object
99+
:param ty_queue: The current stack of types to be used for fuzzing
100+
:return: The dictionary
101+
"""
102+
if not ty_queue:
103+
return {}
104+
105+
ty_queue.insert(0, list) # handle key
106+
key_list = _handle_type(fdp, ty_queue)
107+
ty_queue.insert(0, list) # handle key
108+
val_list = _handle_type(fdp, ty_queue)
109+
110+
# Shrink lists to match
111+
if len(key_list) > len(val_list):
112+
key_list = key_list[:len(val_list)]
113+
elif len(val_list) > len(key_list):
114+
val_list = val_list[:len(key_list)]
115+
116+
return dict(zip(key_list, val_list))
117+
118+
119+
class EnhancedFuzzedDataProvider(atheris.FuzzedDataProvider):
120+
def ConsumeRandomBytes(self) -> bytes:
121+
return self.ConsumeBytes(self.ConsumeIntInRange(0, self.remaining_bytes()))
122+
123+
def ConsumeRandomString(self) -> str:
124+
return self.ConsumeUnicodeNoSurrogates(self.ConsumeIntInRange(0, self.remaining_bytes()))
125+
126+
def ConsumeRemainingString(self) -> str:
127+
return self.ConsumeUnicodeNoSurrogates(self.remaining_bytes())
128+
129+
def ConsumeRemainingBytes(self) -> bytes:
130+
return self.ConsumeBytes(self.remaining_bytes())
131+
132+
@contextlib.contextmanager
133+
def ConsumeMemoryFile(self, all_data: bool = False, as_bytes: bool = True) -> io.BytesIO:
134+
if all_data:
135+
file_data = self.ConsumeRemainingBytes() if as_bytes else self.ConsumeRemainingString()
136+
else:
137+
file_data = self.ConsumeRandomBytes() if as_bytes else self.ConsumeRandomString()
138+
139+
file = io.BytesIO(file_data) if as_bytes else io.StringIO(file_data)
140+
yield file
141+
file.close()
142+
143+
@contextlib.contextmanager
144+
def ConsumeTemporaryFile(self, suffix: str, all_data: bool = False, as_bytes: bool = True) -> io.StringIO:
145+
if all_data:
146+
file_data = self.ConsumeRemainingBytes() if as_bytes else self.ConsumeRemainingString()
147+
else:
148+
file_data = self.ConsumeRandomBytes() if as_bytes else self.ConsumeRandomString()
149+
150+
mode = 'w+b' if as_bytes else 'w+'
151+
tfile = tempfile.NamedTemporaryFile(mode=mode, suffix=suffix)
152+
tfile.write(file_data)
153+
tfile.seek(0)
154+
tfile.flush()
155+
yield tfile.name
156+
tfile.close()

mayhem/fuzz_twb.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env python3
2+
import random
3+
4+
import atheris
5+
import sys
6+
7+
8+
import fuzz_helpers
9+
import random
10+
11+
with atheris.instrument_imports(include=['tableaudocumentapi']):
12+
from tableaudocumentapi import Workbook
13+
14+
from lxml.etree import XMLSyntaxError
15+
from tableaudocumentapi.xfile import TableauVersionNotSupportedException, TableauInvalidFileException
16+
17+
def TestOneInput(data):
18+
fdp = fuzz_helpers.EnhancedFuzzedDataProvider(data)
19+
try:
20+
with fdp.ConsumeTemporaryFile('.twb', all_data=True, as_bytes=True) as filename:
21+
Workbook(filename)
22+
except (XMLSyntaxError, TableauVersionNotSupportedException, ValueError, AttributeError, TableauInvalidFileException):
23+
return -1
24+
except TypeError:
25+
if random.random() > .9:
26+
raise
27+
return -1
28+
def main():
29+
atheris.Setup(sys.argv, TestOneInput)
30+
atheris.Fuzz()
31+
32+
33+
if __name__ == "__main__":
34+
main()

mayhem/testsuite/TABLEAU_93_TWB.twb

Lines change 4D2A d: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?xml version='1.0' encoding='utf-8' ?><workbook source-build='9.3.1 (9300.16.0510.0100)' source-platform='mac' version='9.3' xmlns:user='http://www.tableausoftware.com/xml/user'><datasources><datasource caption='xy (TestV1)' inline='true' name='sqlserver.17u3bqc16tjtxn14e2hxh19tyvpo' version='9.3'><connection authentication='sspi' class='sqlserver' dbname='TestV1' odbc-native-protocol='yes' one-time-sql='' server='mssql2012.test.tsi.lan' username=''></connection></datasource></datasources></workbook>

0 commit comments

Comments
 (0)
0