Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion pyp2rpm/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,28 @@ def rpm_version(version, use_macro=True):
return '{}~{}'.format(rpm_version, rpm_suffix)


def rpm_escape(text):
"""Escapes RPM directives and macros in text to prevent code injection.

RPM spec files interpret percent signs (%) as the start of macros,
directives, or Lua scriptlets. To prevent malicious package metadata
from injecting arbitrary commands, all percent signs must be escaped
by doubling them (%%).

Args:
text: String that may contain user-controlled content

Returns:
String with all percent signs escaped (% becomes %%)
"""
if text is None:
return ''
if not isinstance(text, str):
text = str(text)
# Escape all percent signs by doubling them
return text.replace('%', '%%')


__all__ = [name_for_python_version,
script_name_for_python_version,
sitedir_for_python_version,
Expand All @@ -135,4 +157,5 @@ def rpm_version(version, use_macro=True):
package_to_path,
macroed_url,
rpm_version_410,
rpm_version]
rpm_version,
rpm_escape]
9 changes: 9 additions & 0 deletions pyp2rpm/package_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from pyp2rpm import version
from pyp2rpm import utils
from pyp2rpm import filters

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -55,8 +56,16 @@ def __getattr__(self, name):
return self.data.get(name, 'TODO:')

def __setattr__(self, name, value):
# Fields that need RPM directive escaping (user-controlled metadata)
FIELDS_TO_ESCAPE = ['summary', 'description', 'license', 'home_page', 'name']

if name == 'summary' and isinstance(value, utils.str_classes):
value = value.rstrip('.').replace('\n', ' ')

# Escape RPM directives in user-controlled fields to prevent code injection
if name in FIELDS_TO_ESCAPE and isinstance(value, utils.str_classes):
value = filters.rpm_escape(value)

if value is not None:
self.data[name] = value

Expand Down
Loading