Merge lp:~abentley/bzr-builder/safe into lp:bzr-builder

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 104
Proposed branch: lp:~abentley/bzr-builder/safe
Merge into: lp:bzr-builder
Diff against target: 200 lines (+47/-21)
4 files modified
cmds.py (+21/-6)
recipe.py (+12/-9)
tests/test_blackbox.py (+12/-4)
tests/test_recipe.py (+2/-2)
To merge this branch: bzr merge lp:~abentley/bzr-builder/safe
Reviewer Review Type Date Requested Status
James Westby Pending
Review via email: mp+33685@code.launchpad.net

Commit message

Add --safe option to prevent arbitrary code execution.

Description of the change

This branch adds the --safe option, as we discussed.
It changes forbidden_instructions to permitted_instructions.
It removes the period from the problem, because a period is supplied by
RecipeParseError.
It fixes the error I was getting with an unchanged trunk.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmds.py'
2--- cmds.py 2010-07-09 20:54:37 +0000
3+++ cmds.py 2010-08-25 19:33:04 +0000
4@@ -46,6 +46,7 @@
5 DEBUPSTREAM_VAR,
6 RecipeParser,
7 resolve_revisions,
8+ SAFE_INSTRUCTIONS,
9 )
10
11
12@@ -68,7 +69,13 @@
13 manifest_f.close()
14
15
16-def get_branch_from_recipe_file(recipe_file):
17+def get_branch_from_recipe_file(recipe_file, safe=False):
18+ """Return the base branch for the specified recipe.
19+
20+ :param recipe_file: The URL of the recipe file to retrieve.
21+ :param safe: if True, reject recipes that would cause arbitrary code
22+ execution.
23+ """
24 recipe_transport = transport.get_transport(os.path.dirname(recipe_file))
25 try:
26 recipe_contents = recipe_transport.get_bytes(
27@@ -76,8 +83,12 @@
28 except errors.NoSuchFile:
29 raise errors.BzrCommandError("Specified recipe does not exist: "
30 "%s" % recipe_file)
31+ if safe:
32+ permitted_instructions = SAFE_INSTRUCTIONS
33+ else:
34+ permitted_instructions = None
35 parser = RecipeParser(recipe_contents, filename=recipe_file)
36- return parser.parse()
37+ return parser.parse(permitted_instructions=permitted_instructions)
38
39
40 def get_old_recipe(if_changed_from):
41@@ -318,16 +329,18 @@
42 ]
43
44 def _get_prepared_branch_from_recipe(self, recipe_file,
45- if_changed_from=None):
46+ if_changed_from=None, safe=False):
47 """Common code to prepare a branch and do substitutions.
48
49 :param recipe_file: a path to a recipe file to work from.
50 :param if_changed_from: an optional path to a manifest to
51 compare the recipe against.
52+ :param safe: if True, reject recipes that would cause arbitrary code
53+ execution.
54 :return: A tuple with (retcode, base_branch). If retcode is None
55 then the command execution should continue.
56 """
57- base_branch = get_branch_from_recipe_file(recipe_file)
58+ base_branch = get_branch_from_recipe_file(recipe_file, safe=safe)
59 time = datetime.datetime.utcnow()
60 base_branch.substitute_time(time)
61 old_recipe = None
62@@ -388,6 +401,8 @@
63 Option("append-version", type=str, help="Append the "
64 "specified string to the end of the version used "
65 "in debian/changelog."),
66+ Option("safe", help="Error if the recipe would cause"
67+ " arbitrary code execution.")
68 ]
69
70 takes_args = ["recipe_file", "working_basedir?"]
71@@ -395,7 +410,7 @@
72 def run(self, recipe_file, working_basedir=None, manifest=None,
73 if_changed_from=None, package=None, distribution=None,
74 dput=None, key_id=None, no_build=None, watch_ppa=False,
75- append_version=None):
76+ append_version=None, safe=False):
77
78 if dput is not None and key_id is None:
79 raise errors.BzrCommandError("You must specify --key-id if you "
80@@ -409,7 +424,7 @@
81 target_from_dput(dput)
82
83 result, base_branch = self._get_prepared_branch_from_recipe(recipe_file,
84- if_changed_from=if_changed_from)
85+ if_changed_from=if_changed_from, safe=safe)
86 if result is not None:
87 return result
88 if working_basedir is None:
89
90=== modified file 'recipe.py'
91--- recipe.py 2010-08-16 20:07:15 +0000
92+++ recipe.py 2010-08-25 19:33:04 +0000
93@@ -56,6 +56,9 @@
94 NEST_INSTRUCTION = "nest"
95 RUN_INSTRUCTION = "run"
96
97+SAFE_INSTRUCTIONS = [
98+ MERGE_INSTRUCTION, NEST_PART_INSTRUCTION, NEST_INSTRUCTION]
99+
100 TIME_VAR = "{time}"
101 DATE_VAR = "{date}"
102 REVNO_VAR = "{revno}"
103@@ -770,12 +773,12 @@
104 if filename is None:
105 self.filename = "recipe"
106
107- def parse(self, forbidden_instructions=None):
108+ def parse(self, permitted_instructions=None):
109 """Parse the recipe.
110
111- :param forbidden_instructions: a list of instructions that you
112- don't want to allow. Defaults to None allowing them all.
113- :type forbidden_instructions: list(str) or None
114+ :param permitted_instructions: a list of instructions that you
115+ want to allow. Defaults to None allowing them all.
116+ :type permitted_instructions: list(str) or None
117 :return: a RecipeBranch representing the recipe.
118 """
119 self.lines = self.text.split("\n")
120@@ -819,7 +822,7 @@
121 last_instruction = ""
122 else:
123 instruction = self.parse_instruction(
124- forbidden_instructions=forbidden_instructions)
125+ permitted_instructions=permitted_instructions)
126 if instruction == RUN_INSTRUCTION:
127 self.parse_whitespace("the command")
128 command = self.take_to_newline().strip()
129@@ -868,7 +871,7 @@
130 self.new_line()
131 return version, deb_version
132
133- def parse_instruction(self, forbidden_instructions=None):
134+ def parse_instruction(self, permitted_instructions=None):
135 if self.version < 0.2:
136 options = (MERGE_INSTRUCTION, NEST_INSTRUCTION)
137 options_str = "'%s' or '%s'" % options
138@@ -884,10 +887,10 @@
139 self.throw_parse_error("End of line while looking for %s"
140 % options_str)
141 if instruction in options:
142- if forbidden_instructions is not None:
143- if instruction in forbidden_instructions:
144+ if permitted_instructions is not None:
145+ if instruction not in permitted_instructions:
146 self.throw_parse_error("The '%s' instruction is "
147- "forbidden." % instruction,
148+ "forbidden" % instruction,
149 cls=ForbiddenInstructionError,
150 instruction_name=instruction)
151 self.take_chars(len(instruction))
152
153=== modified file 'tests/test_blackbox.py'
154--- tests/test_blackbox.py 2010-07-09 15:42:23 +0000
155+++ tests/test_blackbox.py 2010-08-25 19:33:04 +0000
156@@ -1,5 +1,5 @@
157-# bzr-builter: a bzr plugin to constuct trees based on recipes
158-# Copyright 2009 Canonical Ltd.
159+# bzr-builder: a bzr plugin to constuct trees based on recipes
160+# Copyright 2009-2010 Canonical Ltd.
161
162 # This program is free software: you can redistribute it and/or modify it
163 # under the terms of the GNU General Public License version 3, as published
164@@ -274,5 +274,13 @@
165 "deb-version $\nsource 1\n")])
166 err = self.run_bzr("dailydeb -q test.recipe working --package foo",
167 retcode=3)[1]
168- self.assertEqual("bzr: ERROR: Invalid deb-version: $: "
169- "Could not parse version: $\n", err)
170+ self.assertContainsRe(err, "bzr: ERROR: Invalid deb-version: \$: ")
171+
172+ def test_cmd_dailydeb_with_safe(self):
173+ self.make_simple_package()
174+ self.build_tree_contents([("test.recipe", "# bzr-builder format 0.3 "
175+ "deb-version 1\nsource 1\nrun something bad")])
176+ out, err = self.run_bzr("dailydeb -q test.recipe working --safe",
177+ retcode=3)
178+ self.assertContainsRe(err, "The 'run' instruction is forbidden.$")
179+
180
181=== modified file 'tests/test_recipe.py'
182--- tests/test_recipe.py 2010-08-12 14:04:50 +0000
183+++ tests/test_recipe.py 2010-08-25 19:33:04 +0000
184@@ -35,7 +35,7 @@
185 RecipeBranch,
186 RecipeParseError,
187 resolve_revisions,
188- RUN_INSTRUCTION,
189+ SAFE_INSTRUCTIONS,
190 )
191
192
193@@ -438,7 +438,7 @@
194 exc = self.assertParseError(3, 1, "The 'run' instruction is "
195 "forbidden.", self.get_recipe, self.basic_header_and_branch
196 + "run touch test\n",
197- forbidden_instructions=[RUN_INSTRUCTION])
198+ permitted_instructions=[SAFE_INSTRUCTIONS])
199 self.assertTrue(isinstance(exc, ForbiddenInstructionError))
200 self.assertEqual("run", exc.instruction_name)
201

Subscribers

People subscribed via source and target branches