VYPR
Moderate severityNVD Advisory· Published Feb 9, 2018· Updated Sep 17, 2024

CVE-2017-10689

CVE-2017-10689

Description

In previous versions of Puppet Agent it was possible to install a module with world writable permissions. Puppet Agent 5.3.4 and 1.10.10 included a fix to this vulnerability.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Puppet Agent allowed world-writable permissions on extracted module files, potentially allowing local privilege escalation or unauthorized modification.

Vulnerability

In Puppet Agent versions prior to 5.3.4 and 1.10.10, the module unpacking code did not reset file permissions when extracting module tarballs [2][3]. This allowed files to retain their original permissions from the archive, which could include world-writable permissions for directories and non-executable files. The vulnerability is identified as CVE-2017-10689 and is documented in NVD [4]. The issue resides in the unpack method of lib/puppet/module_tool/tar.rb.

Exploitation

An attacker with the ability to install a Puppet module (e.g., via puppet module install) could craft a malicious module tarball containing files with world-writable permissions. No special network position is required if the attacker has local access or can trick a user into installing the module. The unpacking process would preserve the world-writable permissions from the archive.

Impact

A local attacker who gains write access to the installed module files could modify them, potentially leading to privilege escalation or arbitrary code execution in the context of the Puppet process or the system's Puppet user. This could compromise the integrity of the system's configuration management.

Mitigation

The fix was included in Puppet Agent versions 5.3.4 and 1.10.10 [2][3]. Red Hat issued an advisory (RHSA-2018:2927) for Red Hat Satellite 6.4 [1]. Users should upgrade to the latest patched versions. If upgrade is not possible, limit module installation to trusted sources.

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
puppetRubyGems
< 4.10.104.10.10
puppetRubyGems
>= 5.0.0, < 5.3.45.3.4

Affected products

7

Patches

2
2f1047f85e22

Merge pull request #2 from puppetlabs/CVE-2017-2295-4.10.x

https://github.com/puppetlabs/puppetSean P McDonaldJan 19, 2018via ghsa
2 files changed · +91 9
  • lib/puppet/module_tool/tar/mini.rb+57 4 modified
    @@ -3,24 +3,77 @@ def unpack(sourcefile, destdir, _)
         Zlib::GzipReader.open(sourcefile) do |reader|
           Archive::Tar::Minitar.unpack(reader, destdir, find_valid_files(reader)) do |action, name, stats|
             case action
    -        when :file_done
    -          File.chmod(0644, "#{destdir}/#{name}")
    -        when :dir, :file_start
    +        when :dir
               validate_entry(destdir, name)
    +          set_dir_mode!(stats)
    +          Puppet.debug("Extracting: #{destdir}/#{name}")
    +        when :file_start
    +          # Octal string of the old file mode.
    +          validate_entry(destdir, name)
    +          set_file_mode!(stats)
               Puppet.debug("Extracting: #{destdir}/#{name}")
             end
    +        set_default_user_and_group!(stats)
    +        stats
           end
         end
       end
     
       def pack(sourcedir, destfile)
         Zlib::GzipWriter.open(destfile) do |writer|
    -      Archive::Tar::Minitar.pack(sourcedir, writer)
    +      Archive::Tar::Minitar.pack(sourcedir, writer) do |step, name, stats|
    +        # TODO smcclellan 2017-10-31 Set permissions here when this yield block
    +        # executes before the header is written. As it stands, the `stats`
    +        # argument isn't mutable in a way that will effect the desired mode for
    +        # the file.
    +      end
         end
       end
     
       private
     
    +  EXECUTABLE = 0755
    +  NOT_EXECUTABLE = 0644
    +  USER_EXECUTE = 0100
    +
    +  def set_dir_mode!(stats)
    +    if stats.key?(:mode)
    +      # This is only the case for `pack`, so this code will not run.
    +      stats[:mode] = EXECUTABLE
    +    elsif stats.key?(:entry)
    +      old_mode = stats[:entry].instance_variable_get(:@mode)
    +      if old_mode.is_a?(Fixnum)
    +        stats[:entry].instance_variable_set(:@mode, EXECUTABLE)
    +      end
    +    end
    +  end
    +
    +  # Sets a file mode to 0755 if the file is executable by the user.
    +  # Sets a file mode to 0644 if the file mode is set (non-Windows).
    +  def sanitized_mode(old_mode)
    +    old_mode & USER_EXECUTE != 0 ? EXECUTABLE : NOT_EXECUTABLE
    +  end
    +
    +  def set_file_mode!(stats)
    +    if stats.key?(:mode)
    +      # This is only the case for `pack`, so this code will not run.
    +      stats[:mode] = sanitized_mode(stats[:mode])
    +    elsif stats.key?(:entry)
    +      old_mode = stats[:entry].instance_variable_get(:@mode)
    +      # If the user can execute the file, set 0755, otherwise 0644.
    +      if old_mode.is_a?(Fixnum)
    +        new_mode = sanitized_mode(old_mode)
    +        stats[:entry].instance_variable_set(:@mode, new_mode)
    +      end
    +    end
    +  end
    +
    +  # Sets UID and GID to 0 for standardization.
    +  def set_default_user_and_group!(stats)
    +    stats[:uid] = 0
    +    stats[:gid] = 0
    +  end
    +
       # Find all the valid files in tarfile.
       #
       # This check was mainly added to ignore 'x' and 'g' flags from the PAX
    
  • spec/unit/module_tool/tar/mini_spec.rb+34 5 modified
    @@ -8,10 +8,17 @@
       let(:destfile)   { '/the/dest/file.tar.gz' }
       let(:minitar)    { described_class.new }
     
    -  it "unpacks a tar file" do
    -    unpacks_the_entry(:file_start, 'thefile')
    +  class MockFileStatEntry
    +    def initialize(mode = 0100)
    +      @mode = mode
    +    end
    +  end
    +
    +  it "unpacks a tar file with correct permissions" do
    +    entry = unpacks_the_entry(:file_start, 'thefile')
     
         minitar.unpack(sourcefile, destdir, 'uid')
    +    expect(entry.instance_variable_get(:@mode)).to eq(0755)
       end
     
       it "does not allow an absolute path" do
    @@ -41,20 +48,42 @@
                          "Attempt to install file with an invalid path into \"#{File.expand_path('/the/thedir')}\" under \"#{destdir}\"")
       end
     
    +  it "unpacks on Windows" do
    +    unpacks_the_entry(:file_start, 'thefile', nil)
    +
    +    entry = minitar.unpack(sourcefile, destdir, 'uid')
    +    # Windows does not use these permissions.
    +    expect(entry.instance_variable_get(:@mode)).to eq(nil)
    +  end
    +
       it "packs a tar file" do
         writer = stub('GzipWriter')
     
         Zlib::GzipWriter.expects(:open).with(destfile).yields(writer)
    -    Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer)
    +    stats = {:mode => 0222}
    +    Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer).yields(:file_start, 'abc', stats)
    +
    +    minitar.pack(sourcedir, destfile)
    +  end
    +
    +  it "packs a tar file on Windows" do
    +    writer = stub('GzipWriter')
    +
    +    Zlib::GzipWriter.expects(:open).with(destfile).yields(writer)
    +    Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer).
    +        yields(:file_start, 'abc', {:entry => MockFileStatEntry.new(nil)})
     
         minitar.pack(sourcedir, destfile)
       end
     
    -  def unpacks_the_entry(type, name)
    +  def unpacks_the_entry(type, name, mode = 0100)
         reader = stub('GzipReader')
     
         Zlib::GzipReader.expects(:open).with(sourcefile).yields(reader)
         minitar.expects(:find_valid_files).with(reader).returns([name])
    -    Archive::Tar::Minitar.expects(:unpack).with(reader, destdir, [name]).yields(type, name, nil)
    +    entry = MockFileStatEntry.new(mode)
    +    Archive::Tar::Minitar.expects(:unpack).with(reader, destdir, [name]).
    +        yields(type, name, {:entry => entry})
    +    entry
       end
     end
    
17d9e02da388

(PUP-7866) Reset permissions when unpacking tar in PMT

https://github.com/puppetlabs/puppetScott McClellanOct 21, 2017via ghsa
2 files changed · +91 9
  • lib/puppet/module_tool/tar/mini.rb+57 4 modified
    @@ -3,24 +3,77 @@ def unpack(sourcefile, destdir, _)
         Zlib::GzipReader.open(sourcefile) do |reader|
           Archive::Tar::Minitar.unpack(reader, destdir, find_valid_files(reader)) do |action, name, stats|
             case action
    -        when :file_done
    -          File.chmod(0644, "#{destdir}/#{name}")
    -        when :dir, :file_start
    +        when :dir
               validate_entry(destdir, name)
    +          set_dir_mode!(stats)
    +          Puppet.debug("Extracting: #{destdir}/#{name}")
    +        when :file_start
    +          # Octal string of the old file mode.
    +          validate_entry(destdir, name)
    +          set_file_mode!(stats)
               Puppet.debug("Extracting: #{destdir}/#{name}")
             end
    +        set_default_user_and_group!(stats)
    +        stats
           end
         end
       end
     
       def pack(sourcedir, destfile)
         Zlib::GzipWriter.open(destfile) do |writer|
    -      Archive::Tar::Minitar.pack(sourcedir, writer)
    +      Archive::Tar::Minitar.pack(sourcedir, writer) do |step, name, stats|
    +        # TODO smcclellan 2017-10-31 Set permissions here when this yield block
    +        # executes before the header is written. As it stands, the `stats`
    +        # argument isn't mutable in a way that will effect the desired mode for
    +        # the file.
    +      end
         end
       end
     
       private
     
    +  EXECUTABLE = 0755
    +  NOT_EXECUTABLE = 0644
    +  USER_EXECUTE = 0100
    +
    +  def set_dir_mode!(stats)
    +    if stats.key?(:mode)
    +      # This is only the case for `pack`, so this code will not run.
    +      stats[:mode] = EXECUTABLE
    +    elsif stats.key?(:entry)
    +      old_mode = stats[:entry].instance_variable_get(:@mode)
    +      if old_mode.is_a?(Fixnum)
    +        stats[:entry].instance_variable_set(:@mode, EXECUTABLE)
    +      end
    +    end
    +  end
    +
    +  # Sets a file mode to 0755 if the file is executable by the user.
    +  # Sets a file mode to 0644 if the file mode is set (non-Windows).
    +  def sanitized_mode(old_mode)
    +    old_mode & USER_EXECUTE != 0 ? EXECUTABLE : NOT_EXECUTABLE
    +  end
    +
    +  def set_file_mode!(stats)
    +    if stats.key?(:mode)
    +      # This is only the case for `pack`, so this code will not run.
    +      stats[:mode] = sanitized_mode(stats[:mode])
    +    elsif stats.key?(:entry)
    +      old_mode = stats[:entry].instance_variable_get(:@mode)
    +      # If the user can execute the file, set 0755, otherwise 0644.
    +      if old_mode.is_a?(Fixnum)
    +        new_mode = sanitized_mode(old_mode)
    +        stats[:entry].instance_variable_set(:@mode, new_mode)
    +      end
    +    end
    +  end
    +
    +  # Sets UID and GID to 0 for standardization.
    +  def set_default_user_and_group!(stats)
    +    stats[:uid] = 0
    +    stats[:gid] = 0
    +  end
    +
       # Find all the valid files in tarfile.
       #
       # This check was mainly added to ignore 'x' and 'g' flags from the PAX
    
  • spec/unit/module_tool/tar/mini_spec.rb+34 5 modified
    @@ -8,10 +8,17 @@
       let(:destfile)   { '/the/dest/file.tar.gz' }
       let(:minitar)    { described_class.new }
     
    -  it "unpacks a tar file" do
    -    unpacks_the_entry(:file_start, 'thefile')
    +  class MockFileStatEntry
    +    def initialize(mode = 0100)
    +      @mode = mode
    +    end
    +  end
    +
    +  it "unpacks a tar file with correct permissions" do
    +    entry = unpacks_the_entry(:file_start, 'thefile')
     
         minitar.unpack(sourcefile, destdir, 'uid')
    +    expect(entry.instance_variable_get(:@mode)).to eq(0755)
       end
     
       it "does not allow an absolute path" do
    @@ -41,20 +48,42 @@
                          "Attempt to install file with an invalid path into \"#{File.expand_path('/the/thedir')}\" under \"#{destdir}\"")
       end
     
    +  it "unpacks on Windows" do
    +    unpacks_the_entry(:file_start, 'thefile', nil)
    +
    +    entry = minitar.unpack(sourcefile, destdir, 'uid')
    +    # Windows does not use these permissions.
    +    expect(entry.instance_variable_get(:@mode)).to eq(nil)
    +  end
    +
       it "packs a tar file" do
         writer = stub('GzipWriter')
     
         Zlib::GzipWriter.expects(:open).with(destfile).yields(writer)
    -    Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer)
    +    stats = {:mode => 0222}
    +    Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer).yields(:file_start, 'abc', stats)
    +
    +    minitar.pack(sourcedir, destfile)
    +  end
    +
    +  it "packs a tar file on Windows" do
    +    writer = stub('GzipWriter')
    +
    +    Zlib::GzipWriter.expects(:open).with(destfile).yields(writer)
    +    Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer).
    +        yields(:file_start, 'abc', {:entry => MockFileStatEntry.new(nil)})
     
         minitar.pack(sourcedir, destfile)
       end
     
    -  def unpacks_the_entry(type, name)
    +  def unpacks_the_entry(type, name, mode = 0100)
         reader = stub('GzipReader')
     
         Zlib::GzipReader.expects(:open).with(sourcefile).yields(reader)
         minitar.expects(:find_valid_files).with(reader).returns([name])
    -    Archive::Tar::Minitar.expects(:unpack).with(reader, destdir, [name]).yields(type, name, nil)
    +    entry = MockFileStatEntry.new(mode)
    +    Archive::Tar::Minitar.expects(:unpack).with(reader, destdir, [name]).
    +        yields(type, name, {:entry => entry})
    +    entry
       end
     end
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

10

News mentions

0

No linked articles in our index yet.