corruption_monkey 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4b6874de9bd4a6d99c99d10e6ffa8ff196d0e380
4
+ data.tar.gz: 74071c21f62bbecb7a1010a0c1facd60eda9ddf9
5
+ SHA512:
6
+ metadata.gz: 9beb55581474d4f6e9ea3976da3c2566c33f24e63d9d21617613a5905188addd44f7cb9648fe021a6ba1de86c04bca409bb5f4e1f58c05b4af6c690c2ff2ee49
7
+ data.tar.gz: a8eaa52a59155d0a93aee547888ac985621f144e6233c7ceb14862a53c64fb62f03bc8ef5969428cc05e03d2a8cdea6af7db887ead36183a7fdfe63a0fc41475
data/.gitignore ADDED
@@ -0,0 +1,32 @@
1
+ # Vim
2
+ [._]*.s[a-w][a-z]
3
+ [._]s[a-w][a-z]
4
+ # session
5
+ Session.vim
6
+ .netrwhist
7
+ *~
8
+ tags
9
+
10
+ # Ruby
11
+ *.gem
12
+ *.rbc
13
+ /.config
14
+ /coverage/
15
+ /InstalledFiles
16
+ /pkg/
17
+ /spec/reports/
18
+ /spec/examples.txt
19
+ /test/tmp/
20
+ /test/version_tmp/
21
+ /tmp/
22
+
23
+ ## Documentation cache and generated files:
24
+ /.yardoc/
25
+ /_yardoc/
26
+ /doc/
27
+ /rdoc/
28
+
29
+ ## Environment normalization:
30
+ /.bundle/
31
+ /vendor/bundle
32
+ /lib/bundler/man/
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.1
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,5 @@
1
+ #### How to contribute to corruption_monkey
2
+
3
+ Open an issue/ pull request :)
4
+
5
+ Thanks! :revolving_hearts:
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'rake'
5
+ gem 'minitest'
6
+ end
7
+
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,21 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ corruption_monkey (0.1.2)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ minitest (5.9.0)
10
+ rake (11.2.2)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ corruption_monkey!
17
+ minitest
18
+ rake
19
+
20
+ BUNDLED WITH
21
+ 1.12.5
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Shopify
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.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ ### Corruption monkey
2
+ TL;DR: do 🔬 with backups (and 🐒)
3
+
4
+ This Ruby library (and CLI) provides an easy way to flip bits in a provided file.
5
+
6
+ It was built in order to corrupt database backups and test if our current and future tools work and calculate their coverage.
7
+
8
+ #### Local installation
9
+ ```bash
10
+ $ gem install corruption_monkey
11
+ ```
12
+ or
13
+ ```bash
14
+ $ git clone https://github.com/Shopify/corruption_monkey && cd corruption_monkey
15
+ $ rake install
16
+ ```
17
+
18
+ #### CLI
19
+ ```bash
20
+ # flip a bit in a specific bit index
21
+ $ flip <file> <position>
22
+
23
+ # flip a bit in some part of the file
24
+ $ flip <file> <percent>%
25
+
26
+ # flip a bit... somewhere ¯\_(ツ)_/¯
27
+ $ flip <file> chaos
28
+
29
+ # show help message with this examples
30
+ $ flip [help]
31
+
32
+ ```
33
+ #### API
34
+ ```ruby
35
+ require 'tempfile'
36
+ require 'logger'
37
+ require 'corruption_monkey'
38
+
39
+ file = Tempfile.new('whatever')
40
+ file_path = file.path
41
+ logger = Logger.new(nil)
42
+
43
+ # flip a bit in a specific bit index
44
+ BitFlipper.random_bit_flip(file_path, bit: 40, logger: logger)
45
+
46
+ # flip a bit in some part of thefile
47
+ BitFlipper.flip_in_range(file_path, percentile: 4, logger: logger)
48
+
49
+ # flip a bit... somewhere ¯\_(ツ)_/¯
50
+ BitFlipper.random_bit_flip(file_path, logger: logger)
51
+
52
+ ```
53
+
54
+ #### Extra
55
+ You can generate png glitch art, too.
56
+ ![image](https://cloud.githubusercontent.com/assets/959128/17756485/235c13ae-64ae-11e6-8583-5e606bcb45f3.png)
57
+
58
+ (From https://en.wikipedia.org/wiki/Portable_Network_Graphics)
59
+
60
+ #### ⚠️Caution!
61
+ This lib may improve your bit flipping skills.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+ require 'bundler/setup'
3
+ require 'bundler/gem_tasks'
4
+
5
+ task default: :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.test_files = FileList['test/*test.rb']
9
+ end
data/bin/flip ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'corruption_monkey/cli'
4
+
5
+ BitFlipper::CLI.start
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH << 'lib'
2
+ require 'corruption_monkey/version'
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = %w(Javier Honduvilla Coto)
6
+ gem.email = %w(javier.honduvilla@shopify.com javierhonduco@gmail.com)
7
+ gem.description = 'Bit flipping tool.'
8
+ gem.summary = 'Library and CLI to flip a bit in a provided file.'
9
+ gem.homepage = 'https://github.com/Shopify/corruption_monkey'
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^test/})
14
+ gem.name = 'corruption_monkey'
15
+ gem.require_paths = ['lib']
16
+
17
+ gem.version = BitFlipper::VERSION
18
+ end
@@ -0,0 +1,67 @@
1
+ require 'corruption_monkey'
2
+
3
+ module BitFlipper::CLI
4
+ DOCS = <<-DOCS
5
+ CLI options:
6
+
7
+ 1. Specific bit flip (by absolute or relative indexing)
8
+ $ flip <file_path> (<bit_index>|<percentage of file>%)
9
+
10
+ 2. Chaos mode (randomized)
11
+ $ flip <file_path> chaos
12
+
13
+ To display help:
14
+ $ flip help
15
+ DOCS
16
+
17
+ def self.banner(s = $stdout)
18
+ s.puts '🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 '
19
+ s.puts '🔥 bit flipper chaos 🙈 CLI🔥'
20
+ s.puts '🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 '
21
+ s.puts
22
+ end
23
+
24
+ def self.start(args = ARGV)
25
+ file_path = args.shift
26
+ second_argument = args.shift
27
+
28
+ # Empty imput or docs request
29
+ if (file_path.nil? && second_argument.nil?) ||
30
+ (!File.file?(file_path) && file_path == 'help')
31
+ puts DOCS
32
+ exit 0
33
+ end
34
+
35
+ # First argument is not a (existing) file
36
+ unless File.file?(file_path)
37
+ $stderr.puts "The file with path=`#{file_path}` does not exist"
38
+ exit 1
39
+ end
40
+
41
+ logger = Logger.new(STDOUT)
42
+ banner
43
+
44
+ case second_argument
45
+ when 'chaos'
46
+ puts 'chaos mode selected'
47
+ puts
48
+ BitFlipper.random_bit_flip(file_path, logger: logger)
49
+ when /([0-9]{1, 2}|100)%/
50
+ # this regex feels... weird
51
+ puts 'percent mode selected'
52
+ puts
53
+ BitFlipper.flip_in_range(file_path,
54
+ percentile: second_argument.to_i,
55
+ logger: logger)
56
+ when /[0-9]+/
57
+ puts 'accurate mode selected'
58
+ puts
59
+ BitFlipper.flip!(file_path,
60
+ bit: second_argument.to_i,
61
+ logger: logger)
62
+ else
63
+ $stderr.puts 'nothing matches!'
64
+ $stderr.puts DOCS
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module BitFlipper
2
+ VERSION = '0.1.2'
3
+ end
@@ -0,0 +1,58 @@
1
+ require 'logger'
2
+
3
+ module BitFlipper
4
+
5
+ BitOutOfBounds = Class.new(Exception)
6
+
7
+ def self.flip!(target, bit:, logger: Logger.new(nil))
8
+ logger.debug "going to flip bit with index=#{bit}"
9
+
10
+ flip_byte, flip_bit = bit.divmod(8)
11
+
12
+ logger.debug "computed [byte, bit]=#{[flip_byte, flip_bit]}"
13
+
14
+ file = File.open(target, 'r+')
15
+
16
+ file.seek(flip_byte, 0)
17
+ byte = file.read(1)
18
+
19
+ if byte.nil? || out_of_range(target, bit)
20
+ raise BitOutOfBounds, "#{bit} is bigger than the offset=#{size_in_bits(target)}"
21
+ end
22
+
23
+ new_byte = [byte.unpack('c').first ^ (1 << flip_bit)].pack('c')
24
+
25
+ logger.debug "readed byte: `#{byte}`: #{byte.unpack('b*').first}"
26
+ logger.debug "writing byte: `#{new_byte}` #{new_byte.unpack('b*').first}"
27
+
28
+ file.seek(-1, IO::SEEK_CUR)
29
+ file.write(new_byte)
30
+
31
+ file.fsync
32
+ file.close
33
+ end
34
+
35
+ def self.random_bit_flip(target, logger: Logger.new(nil))
36
+ computed_bit = rand(0...size_in_bits(target))
37
+ flip!(target, bit: computed_bit, logger: logger)
38
+ end
39
+
40
+ def self.flip_in_range(target, percentile:, logger: Logger.new(nil))
41
+ # TODO(javierhonduco): Improve error checking (╯ರ ~ ರ)
42
+ bit_to_flip = (size_in_bits(target) * percentile/101.0).to_i
43
+
44
+ if out_of_range(target, bit_to_flip)
45
+ raise IndexError, "percentile=#{percentile} not in range"
46
+ end
47
+
48
+ flip!(target, bit: bit_to_flip, logger: logger)
49
+ end
50
+
51
+ def self.size_in_bits(file)
52
+ File.size(file) * 8
53
+ end
54
+
55
+ def self.out_of_range(file, bit)
56
+ bit < 0 || bit > size_in_bits(file)
57
+ end
58
+ end
data/test/cli_test.rb ADDED
@@ -0,0 +1,56 @@
1
+ require 'minitest/autorun'
2
+ require 'fileutils'
3
+ require 'corruption_monkey'
4
+ require_relative 'test_helpers'
5
+
6
+ class TestCLI < Minitest::Test
7
+ include TestHelpers
8
+
9
+ TEST_DIR = "/tmp/bit_flipper_monkey_test_cli_#{rand(0..999)}"
10
+
11
+ def setup
12
+ FileUtils.rm_rf(TEST_DIR) # Ensure clean state
13
+ FileUtils.mkdir(TEST_DIR)
14
+ end
15
+
16
+ def teardown
17
+ FileUtils.rm_rf(TEST_DIR)
18
+ end
19
+
20
+ def test_accurate_bit_flip
21
+ test_file = "#{TEST_DIR}/zeroes"
22
+ zeroes(test_file)
23
+
24
+ run_cli(test_file, '9')
25
+ assert_equal "\u0000\u0002#{"\u0000" * 8}", File.read(test_file)
26
+ end
27
+
28
+ def test_accurate_percentage_flip
29
+ test_file = "#{TEST_DIR}/zeroes"
30
+ zeroes(test_file)
31
+
32
+ run_cli(test_file, '0%')
33
+ assert_equal "\u0001#{"\u0000" * 9}", File.read(test_file)
34
+ end
35
+
36
+ def test_random_bit_flip
37
+ test_file = "#{TEST_DIR}/zeroes"
38
+ zeroes(test_file)
39
+
40
+ run_cli(test_file, 'chaos')
41
+ assert_equal 1, hamming_weight(test_file)
42
+ end
43
+
44
+ def test_help
45
+ run_cli =~ /CLI options:/
46
+ run_cli('help') =~ /CLI options:/
47
+ end
48
+
49
+ private
50
+
51
+ def run_cli(*args)
52
+ output = `bin/flip #{args * ' '}`
53
+ raise ProcessExecutionError unless $?.success?
54
+ output
55
+ end
56
+ end
data/test/lib_test.rb ADDED
@@ -0,0 +1,103 @@
1
+ require 'minitest/autorun'
2
+ require 'fileutils'
3
+ require 'corruption_monkey'
4
+ require_relative 'test_helpers'
5
+
6
+ class TestBitFlipper < Minitest::Test
7
+ include TestHelpers
8
+
9
+ TEST_DIR = "/tmp/bit_flipper_monkey_test_#{rand(0..999)}"
10
+
11
+ def setup
12
+ FileUtils.rm_rf(TEST_DIR) # Ensure clean state
13
+ FileUtils.mkdir(TEST_DIR)
14
+ end
15
+
16
+ def teardown
17
+ FileUtils.rm_rf(TEST_DIR)
18
+ end
19
+
20
+ def test_number_of_changed_bits
21
+ test_file = "#{TEST_DIR}/zeroes"
22
+ zeroes(test_file)
23
+
24
+ BitFlipper.flip!(test_file, bit: 0)
25
+ assert_equal 1, hamming_weight(test_file)
26
+
27
+ BitFlipper.flip!(test_file, bit: 1)
28
+ assert_equal 2, hamming_weight(test_file)
29
+ end
30
+
31
+ def test_basic_ascii_mathz
32
+ test_file = "#{TEST_DIR}/simple_ascii"
33
+
34
+ File.open(test_file, 'w') do |f|
35
+ f.write 'a'
36
+ end
37
+
38
+ BitFlipper.flip!(test_file, bit: 0)
39
+ BitFlipper.flip!(test_file, bit: 1)
40
+ assert_equal 'b', File.read(test_file)
41
+ end
42
+
43
+ def test_basic_zeroes
44
+ test_file = "#{TEST_DIR}/zeroes"
45
+ zeroes(test_file)
46
+
47
+ BitFlipper.flip!(test_file, bit: 9)
48
+ assert_equal "\u0000\u0002#{"\u0000" * 8}", File.read(test_file)
49
+ end
50
+
51
+ def test_twice_flipped_bit
52
+ test_file = "#{TEST_DIR}/zeroes"
53
+ base_file = "#{TEST_DIR}/zeroes_DO_NOT_MODIFY"
54
+
55
+ [test_file, base_file].each do |file|
56
+ zeroes(file)
57
+ end
58
+
59
+ BitFlipper.flip!(test_file, bit: 5)
60
+ BitFlipper.flip!(test_file, bit: 5)
61
+ assert_equal File.read(base_file), File.read(test_file)
62
+
63
+ BitFlipper.flip!(test_file, bit: 2)
64
+ BitFlipper.flip!(test_file, bit: 2)
65
+ assert_equal File.read(base_file), File.read(test_file)
66
+ end
67
+
68
+ def test_percentage
69
+ test_file = "#{TEST_DIR}/zeroes"
70
+ zeroes(test_file)
71
+
72
+ BitFlipper.flip_in_range(test_file, percentile: 0)
73
+ assert_equal "\u0001#{"\u0000" * 9}", File.read(test_file)
74
+ zeroes(test_file)
75
+
76
+ BitFlipper.flip_in_range(test_file, percentile: 50)
77
+ assert_equal "#{"\u0000" * 4}\x80#{"\u0000" * 5}", File.read(test_file)
78
+ zeroes(test_file)
79
+
80
+ BitFlipper.flip_in_range(test_file, percentile: 100)
81
+ assert_equal "#{"\u0000" * 9}\x80", File.read(test_file)
82
+ end
83
+
84
+ def test_bit_out_of_bounds_error
85
+ test_file = "#{TEST_DIR}/ascii_out_of_bounds"
86
+
87
+ File.open(test_file, 'w') do |f|
88
+ f.write 'a'
89
+ end
90
+
91
+ assert_raises BitFlipper::BitOutOfBounds do
92
+ BitFlipper.flip!(test_file, bit: 10)
93
+ end
94
+ end
95
+
96
+ def test_random_bit_flip
97
+ test_file = "#{TEST_DIR}/zeroes"
98
+ zeroes(test_file)
99
+
100
+ BitFlipper.random_bit_flip(test_file)
101
+ assert_equal 1, hamming_weight(test_file)
102
+ end
103
+ end
@@ -0,0 +1,17 @@
1
+ module TestHelpers
2
+ ProcessExecutionError = Class.new(Exception)
3
+
4
+ def zeroes(file)
5
+ `dd if=/dev/zero of=#{file} bs=1 count=10 2> /dev/null`
6
+ raise ProcessExecutionError unless $?.success?
7
+ end
8
+
9
+ def hamming_weight(file)
10
+ File.read(file).
11
+ unpack('b*').
12
+ first.
13
+ split(//).
14
+ map { |b| b == '1' }.
15
+ count(true)
16
+ end
17
+ end
data/travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1
4
+ - 2.3.1
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: corruption_monkey
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Javier
8
+ - Honduvilla
9
+ - Coto
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2016-09-17 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: Bit flipping tool.
16
+ email:
17
+ - javier.honduvilla@shopify.com
18
+ - javierhonduco@gmail.com
19
+ executables:
20
+ - flip
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - ".gitignore"
25
+ - ".ruby-version"
26
+ - CONTRIBUTING.md
27
+ - Gemfile
28
+ - Gemfile.lock
29
+ - LICENSE.md
30
+ - README.md
31
+ - Rakefile
32
+ - bin/flip
33
+ - corruption_monkey.gemspec
34
+ - lib/corruption_monkey.rb
35
+ - lib/corruption_monkey/cli.rb
36
+ - lib/corruption_monkey/version.rb
37
+ - test/cli_test.rb
38
+ - test/lib_test.rb
39
+ - test/test_helpers.rb
40
+ - travis.yml
41
+ homepage: https://github.com/Shopify/corruption_monkey
42
+ licenses: []
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.5.1
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Library and CLI to flip a bit in a provided file.
64
+ test_files:
65
+ - test/cli_test.rb
66
+ - test/lib_test.rb
67
+ - test/test_helpers.rb