S3 Fuzzer: Start Fuzzing

- tweak yaml decision graph
- add test setup bucket creation, etc.
- add output with varying levels of verbosity
This commit is contained in:
Kyle Marsh 2011-08-19 14:54:24 -07:00
parent 76956d86e4
commit 23fee1476a
3 changed files with 162 additions and 77 deletions

View file

@ -1,14 +1,14 @@
start: start:
set: set:
garbage: garbage:
- {random 10-3000 printable} - '{random 10-3000 printable}'
- {random 10-1000 binary} - '{random 10-1000 binary}'
choices: choices:
- bucket - bucket
bucket: bucket:
set: set:
urlpath: /{bucket} urlpath: '/{bucket}'
choices: choices:
- 13 bucket_get - 13 bucket_get
- 8 bucket_put - 8 bucket_put
@ -18,12 +18,12 @@ bucket:
garbage_method: garbage_method:
set: set:
method: method:
- {random 1-100 printable} - '{random 1-100 printable}'
- {random 10-100 binary} - '{random 10-100 binary}'
urlpath: urlpath:
- /{bucket} - '/{bucket}'
- /{bucket}/{object} - '/{bucket}/{object}'
- {random 10-1000 binary} - '{random 10-1000 binary}'
choices: choices:
- bucket_get_simple - bucket_get_simple
- bucket_get_filtered - bucket_get_filtered
@ -36,23 +36,23 @@ bucket_delete:
set: set:
method: DELETE method: DELETE
bucket: bucket:
- {bucket_writable} - '{bucket_writable}'
- {bucket_not_writable} - '{bucket_not_writable}'
- 2 {garbage} - '2 {garbage}'
query: query:
- null - null
- policy - policy
- website - website
- 2 {garbage} - '2 {garbage}'
choices: [] choices: []
bucket_get: bucket_get:
set: set:
method: GET method: GET
bucket: bucket:
- {bucket_readable} - '{bucket_readable}'
- {bucket_not_readable} - '{bucket_not_readable}'
- 2 {garbage} - '2 {garbage}'
choices: choices:
- 11 bucket_get_simple - 11 bucket_get_simple
- bucket_get_filtered - bucket_get_filtered
@ -70,17 +70,17 @@ bucket_get_simple:
- requestPayment - requestPayment
- versioning - versioning
- website - website
- 2 {garbage} - '2 {garbage}'
choices: [] choices: []
bucket_get_uploads: bucket_get_uploads:
set: set:
delimiter: delimiter:
- null - null
- 3 'delimiter={garbage}' - '3 delimiter={garbage}'
prefix: prefix:
- null - null
- 3 'prefix={garbage}' - '3 prefix={garbage}'
key_marker: key_marker:
- null - null
- 'key-marker={object_readable}' - 'key-marker={object_readable}'
@ -93,12 +93,12 @@ bucket_get_uploads:
- 'max-uploads={random 1-1000 digits}' - 'max-uploads={random 1-1000 digits}'
upload_id_marker: upload_id_marker:
- null - null
- 3 'upload-id-marker={random}' - '3 upload-id-marker={random}'
query: query:
- 'uploads' - 'uploads'
- 'uploads&{delimiter}&{prefix}' - 'uploads&{delimiter}&{prefix}'
- 'uploads&{max_uploads}&{key_marker}&{upload_id_marker}' - 'uploads&{max_uploads}&{key_marker}&{upload_id_marker}'
- 2 {garbage} - '2 {garbage}'
choices: [] choices: []
bucket_get_filtered: bucket_get_filtered:
@ -119,15 +119,15 @@ bucket_get_filtered:
- null - null
- '{delimiter}&{prefix}' - '{delimiter}&{prefix}'
- '{max-keys}&{marker}' - '{max-keys}&{marker}'
- 2 {garbage} - '2 {garbage}'
choices: [] choices: []
bucket_put: bucket_put:
set: set:
bucket: bucket:
- {bucket_writable} - '{bucket_writable}'
- {bucket_not_writable} - '{bucket_not_writable}'
- 2 {garbage} - '2 {garbage}'
method: PUT method: PUT
choices: choices:
- bucket_put_simple - bucket_put_simple
@ -137,12 +137,12 @@ bucket_put:
bucket_put_create: bucket_put_create:
set: set:
body: body:
- 2 {garbage} - '2 {garbage}'
- '<CreateBucketConfiguration><LocationConstraint>{random 2-10 binary}</LocationConstraint></CreateBucketConfiguration>' - '<CreateBucketConfiguration><LocationConstraint>{random 2-10 binary}</LocationConstraint></CreateBucketConfiguration>'
acl: acl:
- private - private
- {random 3000 letters} - '{random 3000 letters}'
- {random 100-1000 binary} - '{random 100-1000 binary}'
headers: headers:
- ['0-1', 'x-amz-acl', '{acl}'] - ['0-1', 'x-amz-acl', '{acl}']
choices: [] choices: []
@ -150,8 +150,8 @@ bucket_put_create:
bucket_put_versioning: bucket_put_versioning:
set: set:
body: body:
- {garbage} - '{garbage}'
- 4 '<VersioningConfiguration>{versioning_status}{mfa_delete_body}</VersioningConfiguration>' - '4 <VersioningConfiguration>{versioning_status}{mfa_delete_body}</VersioningConfiguration>'
mfa_delete_body: mfa_delete_body:
- null - null
- '<Status>{random 2-10 binary}</Status>' - '<Status>{random 2-10 binary}</Status>'
@ -169,38 +169,38 @@ bucket_put_versioning:
bucket_put_simple: bucket_put_simple:
set: set:
body: body:
- {acl_body} - '{acl_body}'
- {policy_body} - '{policy_body}'
- {logging_body} - '{logging_body}'
- {notification_body} - '{notification_body}'
- {request_payment_body} - '{request_payment_body}'
- {website_body} - '{website_body}'
acl_body: acl_body:
- null - null
- '<AccessControlPolicy>{owner}{acl}</AccessControlPolicy>' - '<AccessControlPolicy>{owner}{acl}</AccessControlPolicy>'
owner: owner:
- null - null
- 7 '<Owner>{id}{display_name}</Owner>' - '7 <Owner>{id}{display_name}</Owner>'
id: id:
- null - null
- '<ID>{random 10-200 binary}</ID>' - '<ID>{random 10-200 binary}</ID>'
- '<ID>{random 1000-3000 printable}</ID>' - '<ID>{random 1000-3000 printable}</ID>'
display_name: display_name:
- null - null
- 2 '<DisplayName>{random 10-200 binary}</DisplayName>' - '2 <DisplayName>{random 10-200 binary}</DisplayName>'
- 2 '<DisplayName>{random 1000-3000 printable}</DisplayName>' - '2 <DisplayName>{random 1000-3000 printable}</DisplayName>'
- 2 '<DisplayName>{random 10-300 letters}@{random 10-300 letters}.{random 2-4 letters}</DisplayName>' - '2 <DisplayName>{random 10-300 letters}@{random 10-300 letters}.{random 2-4 letters}</DisplayName>'
acl: acl:
- null - null
- 10 '<AccessControlList><Grant>{grantee}{permission}</Grant></AccessControlList>' - '10 <AccessControlList><Grant>{grantee}{permission}</Grant></AccessControlList>'
grantee: grantee:
- null - null
- 7 '<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CanonicalUser">{id}{display_name}</Grantee>' - '7 <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CanonicalUser">{id}{display_name}</Grantee>'
permission: permission:
- null - null
- 7 '<Permission>{permission_value}</Permission>' - '7 <Permission>{permission_value}</Permission>'
permission_value: permission_value:
- 2 {garbage} - '2 {garbage}'
- FULL_CONTROL - FULL_CONTROL
- WRITE - WRITE
- WRITE_ACP - WRITE_ACP
@ -208,7 +208,7 @@ bucket_put_simple:
- READ_ACP - READ_ACP
policy_body: policy_body:
- null - null
- 2 {garbage} - '2 {garbage}'
logging_body: logging_body:
- null - null
- '<BucketLoggingStatus xmlns="http://doc.s3.amazonaws.com/2006-03-01" />' - '<BucketLoggingStatus xmlns="http://doc.s3.amazonaws.com/2006-03-01" />'
@ -219,31 +219,31 @@ bucket_put_simple:
- '<TargetPrefix>{random 10-1000 binary}</TargetPrefix>' - '<TargetPrefix>{random 10-1000 binary}</TargetPrefix>'
target_grants: target_grants:
- null - null
- 10 '<TargetGrants><Grant>{grantee}{permission}</Grant></TargetGrants>' - '10 <TargetGrants><Grant>{grantee}{permission}</Grant></TargetGrants>'
notification_body: notification_body:
- null - null
- '<NotificationConfiguration />' - '<NotificationConfiguration />'
- 2 '<NotificationConfiguration><TopicConfiguration>{topic}{event}</TopicConfiguration}</NotificationConfiguration>' - '2 <NotificationConfiguration><TopicConfiguration>{topic}{event}</TopicConfiguration}</NotificationConfiguration>'
topic: topic:
- null - null
- 2 '<Topic>{garbage}</Topic>' - '2 <Topic>{garbage}</Topic>'
event: event:
- null - null
- '<Event>s3:ReducedRedundancyLostObject</Event>' - '<Event>s3:ReducedRedundancyLostObject</Event>'
- 2 '<Event>{garbage}</Event>' - '2 <Event>{garbage}</Event>'
request_payment_body: request_payment_body:
- null - null
- '<RequestPaymentConfiguration xlmns="http://s3.amazonaws.com/doc/2006-03-01/"><Payer>{payer}</Payer></RequestPaymentConfiguration>' - '<RequestPaymentConfiguration xlmns="http://s3.amazonaws.com/doc/2006-03-01/"><Payer>{payer}</Payer></RequestPaymentConfiguration>'
payer: payer:
- Requester - Requester
- BucketOwner - BucketOwner
- 2 {garbage} - '2 {garbage}'
website_body: website_body:
- null - null
- '<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><IndexDocument><Suffix>{suffix}</Suffix><IndexDocument>{error_doc}<WebsiteConfiguration/>' - '<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><IndexDocument><Suffix>{suffix}</Suffix><IndexDocument>{error_doc}<WebsiteConfiguration/>'
suffix: suffix:
- null - null
- 2 {garbage} - '2 {garbage}'
- '{random 2-10 printable}.html' - '{random 2-10 printable}.html'
error_doc: error_doc:
- null - null

View file

@ -102,6 +102,12 @@ def build_graph():
return graph return graph
#def test_foo():
#graph_file = open('request_decision_graph.yml', 'r')
#graph = yaml.safe_load(graph_file)
#eq(graph['bucket_put_simple']['set']['grantee'], 0)
def test_load_graph(): def test_load_graph():
graph_file = open('request_decision_graph.yml', 'r') graph_file = open('request_decision_graph.yml', 'r')
graph = yaml.safe_load(graph_file) graph = yaml.safe_load(graph_file)
@ -257,6 +263,11 @@ def test_expand_recursive_not_too_eager():
eq(got, 100*'bar') eq(got, 100*'bar')
def test_make_choice_unweighted_with_space():
prng = random.Random(1)
choice = make_choice(['foo bar'], prng)
eq(choice, 'foo bar')
def test_weighted_choices(): def test_weighted_choices():
graph = build_graph() graph = build_graph()
prng = random.Random(1) prng = random.Random(1)
@ -354,7 +365,7 @@ def test_expand_headers():
decision = descend_graph(graph, 'node1', prng) decision = descend_graph(graph, 'node1', prng)
expanded_headers = expand_headers(decision, prng) expanded_headers = expand_headers(decision, prng)
for header, value in expanded_headers: for header, value in expanded_headers.iteritems():
if header == 'my-header': if header == 'my-header':
assert_true(value in ['h1', 'h2', 'h3']) assert_true(value in ['h1', 'h2', 'h3'])
elif header.startswith('random-header-'): elif header.startswith('random-header-'):

View file

@ -1,4 +1,5 @@
from boto.s3.connection import S3Connection from boto.s3.connection import S3Connection
from boto.s3.key import Key
from optparse import OptionParser from optparse import OptionParser
from boto import UserAgent from boto import UserAgent
from . import common from . import common
@ -103,11 +104,11 @@ def make_choice(choices, prng):
continue continue
try: try:
(weight, value) = option.split(None, 1) (weight, value) = option.split(None, 1)
weight = int(weight)
except ValueError: except ValueError:
weight = '1' weight = 1
value = option value = option
weight = int(weight)
if value == 'null' or value == 'None': if value == 'null' or value == 'None':
value = '' value = ''
@ -118,11 +119,11 @@ def make_choice(choices, prng):
def expand_headers(decision, prng): def expand_headers(decision, prng):
expanded_headers = [] expanded_headers = {}
for header in decision['headers']: for header in decision['headers']:
h = expand(decision, header[0], prng) h = expand(decision, header[0], prng)
v = expand(decision, header[1], prng) v = expand(decision, header[1], prng)
expanded_headers.append([h, v]) expanded_headers[h] = v
return expanded_headers return expanded_headers
@ -200,6 +201,8 @@ def parse_options():
parser.add_option('--seed', dest='seed', type='int', help='initial seed for the random number generator', metavar='SEED') parser.add_option('--seed', dest='seed', type='int', help='initial seed for the random number generator', metavar='SEED')
parser.add_option('--seed-file', dest='seedfile', help='read seeds for specific requests from FILE', metavar='FILE') parser.add_option('--seed-file', dest='seedfile', help='read seeds for specific requests from FILE', metavar='FILE')
parser.add_option('-n', dest='num_requests', type='int', help='issue NUM requests before stopping', metavar='NUM') parser.add_option('-n', dest='num_requests', type='int', help='issue NUM requests before stopping', metavar='NUM')
parser.add_option('-v', '--verbose', dest='verbose', action="store_true", help='turn on verbose output')
parser.add_option('-d', '--debug', dest='debug', action="store_true", help='turn on debugging (very verbose) output')
parser.add_option('--decision-graph', dest='graph_filename', help='file in which to find the request decision graph', metavar='NUM') parser.add_option('--decision-graph', dest='graph_filename', help='file in which to find the request decision graph', metavar='NUM')
parser.set_defaults(num_requests=5) parser.set_defaults(num_requests=5)
@ -215,56 +218,127 @@ def randomlist(seed=None):
yield rng.random() yield rng.random()
def populate_buckets(conn, alt):
""" Creates buckets and keys for fuzz testing and sets appropriate
permissions. Returns a dictionary of the bucket and key names.
"""
breadable = common.get_new_bucket(alt)
bwritable = common.get_new_bucket(alt)
bnonreadable = common.get_new_bucket(alt)
oreadable = Key(breadable)
owritable = Key(bwritable)
ononreadable = Key(breadable)
oreadable.set_contents_from_string('oreadable body')
owritable.set_contents_from_string('owritable body')
ononreadable.set_contents_from_string('ononreadable body')
breadable.set_acl('public-read')
bwritable.set_acl('public-read-write')
bnonreadable.set_acl('private')
oreadable.set_acl('public-read')
owritable.set_acl('public-read-write')
ononreadable.set_acl('private')
return dict(
bucket_readable=breadable.name,
bucket_writable=bwritable.name,
bucket_not_readable=bnonreadable.name,
bucket_not_writable=breadable.name,
object_readable=oreadable.key,
object_writable=owritable.key,
object_not_readable=ononreadable.key,
object_not_writable=oreadable.key,
)
def _main(): def _main():
""" The main script """ The main script
""" """
(options, args) = parse_options() (options, args) = parse_options()
random.seed(options.seed if options.seed else None) random.seed(options.seed if options.seed else None)
s3_connection = common.s3.main s3_connection = common.s3.main
alt_connection = common.s3.alt
if options.outfile:
OUT = open(options.outfile, 'w')
else:
OUT = sys.stderr
VERBOSE = DEBUG = open('/dev/null', 'w')
if options.verbose:
VERBOSE = OUT
if options.debug:
DEBUG = OUT
VERBOSE = OUT
request_seeds = None request_seeds = None
if options.seedfile: if options.seedfile:
FH = open(options.seedfile, 'r') FH = open(options.seedfile, 'r')
request_seeds = FH.readlines() request_seeds = [float(line) for line in FH.readlines()]
print>>OUT, 'Seedfile: %s' %options.seedfile
print>>OUT, 'Number of requests: %d' %len(request_seeds)
else: else:
if options.seed:
print>>OUT, 'Initial Seed: %d' %options.seed
print>>OUT, 'Number of requests: %d' %options.num_requests
random_list = randomlist(options.seed) random_list = randomlist(options.seed)
request_seeds = itertools.islice(random_list, options.num_requests) request_seeds = itertools.islice(random_list, options.num_requests)
print>>OUT, 'Decision Graph: %s' %options.graph_filename
graph_file = open(options.graph_filename, 'r') graph_file = open(options.graph_filename, 'r')
decision_graph = yaml.safe_load(graph_file) decision_graph = yaml.safe_load(graph_file)
constants = dict( constants = populate_buckets(s3_connection, alt_connection)
bucket_readable='TODO-breadable', print>>VERBOSE, "Test Buckets/Objects:"
bucket_not_readable='TODO-bnonreadable', for key, value in constants.iteritems():
bucket_writable='TODO-bwritable', print>>VERBOSE, "\t%s: %s" %(key, value)
bucket_not_writable='TODO-bnonwritable',
object_readable='TODO-oreadable',
object_not_readable='TODO-ononreadable',
object_writable='TODO-owritable',
object_not_writable='TODO-ononwritable',
)
print>>OUT, "Begin Fuzzing..."
print>>VERBOSE, '='*80
for request_seed in request_seeds: for request_seed in request_seeds:
print>>OUT, request_seed
prng = random.Random(request_seed) prng = random.Random(request_seed)
decision = assemble_decision(decision_graph, prng) decision = assemble_decision(decision_graph, prng)
decision.update(constants) decision.update(constants)
method = expand(decision, decision['method'], prng) method = expand(decision, decision['method'], prng)
path = expand(decision, decision['path'], prng) path = expand(decision, decision['urlpath'], prng)
try:
body = expand(decision, decision['body'], prng) body = expand(decision, decision['body'], prng)
except KeyError:
body = ''
try:
headers = expand_headers(decision, prng) headers = expand_headers(decision, prng)
except KeyError:
headers = {}
print "Method: %s" % method response = s3_connection.make_request(method, path, data=body, headers=headers, override_num_retries=0)
print "Path: %s" % path
print "Headers: %s" % headers
print ""
print "Body: %s" % body
#response = s3_connection.make_request(method, path, data=body, headers=headers, override_num_retries=0)
print>>VERBOSE, "%s %s" %(method[:100], path[:100])
for h, v in headers.iteritems():
print>>VERBOSE, "%s: %s" %(h[:50], v[:50])
print>>VERBOSE, "%s\n" % body[:100]
print>>DEBUG, 'FULL REQUEST'
print>>DEBUG, 'Method: %r' %method
print>>DEBUG, 'Path: %r' %path
print>>DEBUG, 'Headers:'
for h, v in headers.iteritems():
print>>DEBUG, "\t%r: %r" %(h, v)
print>>DEBUG, 'Body: %r' %body
print>>VERBOSE, 'Response status code: %d %s' %(response.status, response.reason)
print>>DEBUG, 'Body:\n%s' %response.read()
if response.status == 500 or response.status == 503: if response.status == 500 or response.status == 503:
print 'Request generated with seed %d failed:\n%s' % (request_seed, request) print>>OUT, 'FAILED:\n%s' %request
pass print>>VERBOSE, '='*80
print>>OUT, '...done fuzzing'
common.teardown()
def main(): def main():