diff --git a/tests/image-fuzzer/qcow2/__init__.py b/tests/image-fuzzer/qcow2/__init__.py
new file mode 100644
index 0000000..e2ebe19
--- /dev/null
+++ b/tests/image-fuzzer/qcow2/__init__.py
@@ -0,0 +1 @@
+from layout import create_image
diff --git a/tests/image-fuzzer/qcow2/fuzz.py b/tests/image-fuzzer/qcow2/fuzz.py
new file mode 100644
index 0000000..214dd7c
--- /dev/null
+++ b/tests/image-fuzzer/qcow2/fuzz.py
@@ -0,0 +1,271 @@
+# Fuzzing functions for qcow2 fields
+#
+# Copyright (C) 2014 Maria Kustova <maria.k@catit.be>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import random
+
+
+UINT32 = 2**32 - 1
+UINT64 = 2**64 - 1
+# Most significant bit orders
+UINT32_M = 31
+UINT64_M = 63
+
+
+def random_from_intervals(intervals):
+    """Select a random integer number from the list of specified intervals
+
+    Each interval is a tuple of lower and upper limits of the interval. The
+    limits are included. Intervals in a list should not overlap.
+    """
+    total = reduce(lambda x, y: x + y[1] - y[0] + 1, intervals, 0)
+    r = random.randint(0, total-1) + intervals[0][0]
+    temp = zip(intervals, intervals[1:])
+    for x in temp:
+        r = r + (r > x[0][1])*(x[1][0] - x[0][1] - 1)
+    return r
+
+
+def random_bits(bit_ranges):
+    """Generate random binary mask with ones in the specified bit ranges
+
+    Each bit_ranges is a list of tuples of lower and upper limits of bit
+    positions will be fuzzed. The limits are included. Random amount of bits
+    in range limits will be set to ones. The mask is returned in decimal
+    integer format.
+    """
+    bit_numbers = []
+    # Select random amount of random positions in bit_ranges
+    for rng in bit_ranges:
+        bit_numbers += random.sample(range(rng[0], rng[1] + 1),
+                                     random.randint(0, rng[1] - rng[0] + 1))
+    val = 0
+    # Set bits on selected possitions to ones
+    for bit in bit_numbers:
+        val |= 1 << bit
+    return val
+
+
+def validator(current, intervals):
+    """Return a random value from intervals not equal to the current.
+
+    This function is useful for selection from valid values except current one.
+    """
+    val = random_from_intervals(intervals)
+    if val == current:
+        return validator(current, intervals)
+    else:
+        return val
+
+
+def bit_validator(current, bit_ranges):
+    """Return a random bit mask not equal to the current.
+
+    This function is useful for selection from valid values except current one.
+    """
+
+    val = random_bits(bit_ranges)
+    if val == current:
+        return bit_validator(current, bit_ranges)
+    else:
+        return val
+
+
+def selector(current, constraints, is_bitmask=None):
+    """Select one value from all defined by constraints
+
+    Each constraint produces one random value satisfying to it. The function
+    randomly selects one value satisfying at least one constraint (depending on
+    constraints overlaps).
+    """
+    if is_bitmask is None:
+        validate = validator
+    else:
+        validate = bit_validator
+
+    def iter_validate(c):
+        """Apply validate() only to constraints represented as lists
+
+        This auxiliary function replaces short circuit conditions not supported
+        in Python 2.4
+        """
+        if type(c) == list:
+            return validate(current, c)
+        else:
+            return c
+    fuzz_values = [iter_validate(c) for c in constraints]
+    # Remove current for cases it's implicitly specified in constraints
+    # Duplicate validator functionality to prevent decreasing of probability
+    # to get one of allowable values
+    # TODO: remove validators after implementation of intelligent selection
+    # of fields will be fuzzed
+    try:
+        fuzz_values.remove(current)
+    except ValueError:
+        pass
+    return random.choice(fuzz_values)
+
+
+def magic(current):
+    """Fuzz magic header field
+
+    The function just returns the current magic value and provides uniformity
+    of calls for all fuzzing functions
+    """
+    return current
+
+
+def version(current):
+    """Fuzz version header field"""
+    constraints = [
+        [(2, 3)],  # correct values
+        [(0, 1), (4, UINT32)]
+    ]
+    return selector(current, constraints)
+
+
+def backing_file_offset(current):
+    """Fuzz backing file offset header field"""
+    constraints = [
+        [(0, UINT64)]
+    ]
+    return selector(current, constraints)
+
+
+def backing_file_size(current):
+    """Fuzz backing file size header field"""
+    constraints = [
+        [(0, UINT32)]
+    ]
+    return selector(current, constraints)
+
+
+def cluster_bits(current):
+    """Fuzz cluster bits header field"""
+    constraints = [
+        [(9, 20)],  # correct values
+        [(0, 9), (20, UINT32)]
+    ]
+    return selector(current, constraints)
+
+
+def size(current):
+    """Fuzz image size header field"""
+    constraints = [
+        [(0, UINT64)]
+    ]
+    return selector(current, constraints)
+
+
+def crypt_method(current):
+    """Fuzz crypt method header field"""
+    constraints = [
+        [(0, 1)],
+        [(2, UINT32)]
+    ]
+    return selector(current, constraints)
+
+
+def l1_size(current):
+    """Fuzz L1 table size header field"""
+    constraints = [
+        [(0, UINT32)]
+    ]
+    return selector(current, constraints)
+
+
+def l1_table_offset(current):
+    """Fuzz L1 table offset header field"""
+    constraints = [
+        [(0, UINT64)]
+    ]
+    return selector(current, constraints)
+
+
+def refcount_table_offset(current):
+    """Fuzz refcount table offset header field"""
+    constraints = [
+        [(0, UINT64)]
+    ]
+    return selector(current, constraints)
+
+
+def refcount_table_clusters(current):
+    """Fuzz refcount table clusters header field"""
+    constraints = [
+        [(0, UINT32)]
+    ]
+    return selector(current, constraints)
+
+
+def nb_snapshots(current):
+    """Fuzz number of snapshots header field"""
+    constraints = [
+        [(0, UINT32)]
+    ]
+    return selector(current, constraints)
+
+
+def snapshots_offset(current):
+    """Fuzz snapshots offset header field"""
+    constraints = [
+        [(0, UINT64)]
+    ]
+    return selector(current, constraints)
+
+
+def incompatible_features(current):
+    """Fuzz incompatible features header field"""
+    constraints = [
+        [(0, 1)],  # allowable values
+        [(0, UINT64_M)]
+    ]
+    return selector(current, constraints, 1)
+
+
+def compatible_features(current):
+    """Fuzz compatible features header field"""
+    constraints = [
+        [(0, UINT64_M)]
+    ]
+    return selector(current, constraints, 1)
+
+
+def autoclear_features(current):
+    """Fuzz autoclear features header field"""
+    constraints = [
+        [(0, UINT64_M)]
+    ]
+    return selector(current, constraints, 1)
+
+
+def refcount_order(current):
+    """Fuzz number of refcount order header field"""
+    constraints = [
+        [(0, UINT32)]
+    ]
+    return selector(current, constraints)
+
+
+def header_length(current):
+    """Fuzz number of refcount order header field"""
+    constraints = [
+        72,
+        104,
+        [(0, UINT32)]
+    ]
+    return selector(current, constraints)
diff --git a/tests/image-fuzzer/qcow2/layout.py b/tests/image-fuzzer/qcow2/layout.py
new file mode 100644
index 0000000..8bb65a8
--- /dev/null
+++ b/tests/image-fuzzer/qcow2/layout.py
@@ -0,0 +1,125 @@
+# Generator of fuzzed qcow2 images
+#
+# Copyright (C) 2014 Maria Kustova <maria.k@catit.be>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import random
+import struct
+import fuzz
+
+
+MAX_IMAGE_SIZE = 10*2**20
+
+
+def fuzz_struct(structure):
+    """Select part of fields in the specified structure and assign them invalid
+    values
+
+    From 20% to 50% of all fields will be randomly selected and fuzzed
+    """
+    extract = random.sample(structure,
+                            random.randint(len(structure)/5, len(structure)/2))
+
+    def iter_fuzz(field):
+        """Fuzz field value if it's selected
+
+        This auxiliary function replaces short circuit conditions not supported
+        in Python 2.4
+        """
+        if field in extract:
+            return field[0:2] + [getattr(fuzz, field[3])(field[2])] \
+                + field[-1:]
+        else:
+            return field
+
+    return [iter_fuzz(field) for field in structure]
+
+
+def image_size():
+    """Generate a random file size aligned to a random correct cluster size"""
+    cluster_bits = random.randrange(9, 21)
+    cluster_size = 1 << cluster_bits
+    file_size = random.randrange(5*cluster_size,
+                                 MAX_IMAGE_SIZE + 1,
+                                 cluster_size)
+    return [cluster_bits, file_size]
+
+
+def header(cluster_bits, img_size):
+    """Generate a random valid header"""
+    magic = "QFI\xfb"
+    version = random.randint(2, 3)
+    # Next two set to zero while qcow emulation is not supported
+    backing_file_offset = 0
+    backing_file_size = 0
+    crypt_method = random.randint(0, 1)
+    # All below are zeroes while a corresponding feature is not supported
+    l1_size = 0
+    l1_table_offset = 0
+    refcount_table_offset = 0
+    refcount_table_clusters = 0
+    nb_snapshots = 0
+    snapshots_offset = 0
+    # From the e-mail thread for [PATCH] docs: Define refcount_bits value:
+    # Only refcount_order = 4 is supported by QEMU at the moment
+    refcount_order = 4
+    autoclear_features = 0  # doesn't depend on version
+    if version == 2:
+        incompatible_features = 0
+        compatible_features = 0
+        header_length = 72
+    else:
+        incompatible_features = random.getrandbits(2)
+        compatible_features = random.getrandbits(1)
+        header_length = 104
+
+    return [
+        [0, '>4s', magic, 'magic'],
+        [4, '>I', version, 'version'],
+        [8, '>Q', backing_file_offset, 'backing_file_offset'],
+        [16, '>I', backing_file_size, 'backing_file_size'],
+        [20, '>I', cluster_bits, 'cluster_bits'],
+        [24, '>Q', img_size, 'size'],
+        [32, '>I', crypt_method, 'crypt_method'],
+        [36, '>I', l1_size, 'l1_size'],
+        [40, '>Q', l1_table_offset, 'l1_table_offset'],
+        [48, '>Q', refcount_table_offset, 'refcount_table_offset'],
+        [56, '>I', refcount_table_clusters, 'refcount_table_clusters'],
+        [60, '>I', nb_snapshots, 'nb_snapshots'],
+        [64, '>Q', snapshots_offset, 'snapshots_offset'],
+        [72, '>Q', incompatible_features, 'incompatible_features'],
+        [80, '>Q', compatible_features, 'compatible_features'],
+        [88, '>Q', autoclear_features, 'autoclear_features'],
+        [96, '>I', refcount_order, 'refcount_order'],
+        [100, '>I', header_length, 'header_length']
+    ]
+
+
+def create_image(test_img_path):
+    """Write a fuzzed image to the specified file"""
+    image_file = open(test_img_path, 'w')
+    cluster_bits, v_image_size = image_size()
+    # Create an empty image
+    # (sparse if FS supports it or preallocated otherwise)
+    image_file.seek(v_image_size - 1)
+    image_file.write("\0")
+    v_header = header(cluster_bits, v_image_size)  # create a valid header
+    v_header = fuzz_struct(v_header)  # fuzz the header
+
+    for field in v_header:
+        image_file.seek(field[0])
+        image_file.write(struct.pack(field[1], field[2]))
+    image_file.close()
