CVE-2024-27294 (published 2024-02-29T23:15Z)

dp-golang is a Puppet module for Go installations.  Prior to 1.2.7, dp-golang could install files — including the compiler binary — with the wrong ownership when Puppet was run as root and the installed package was On macOS: Go version 1.4.3 through 1.21rc3, inclusive, go1.4-bootstrap-20170518.tar.gz, or go1.4-bootstrap-20170531.tar.gz. The user and group specified in Puppet code were ignored for files within the archive. dp-puppet version 1.2.7 will recreate installations if the owner or group of any file or directory within that installation does not match the requested owner or group


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/danielparks/puppet-golang
commit_details: repo_url: https://github.com/danielparks/puppet-golang
author: Daniel Parks
author_date: 2024-02-21 17:27:58-08:00
msg: Security: ignore file ownership when extracting Go

Certain Go tarballs specified that some files, such as the `go` binary,
were supposed to be owned by a non-root user. When running as root,
dp-golang extracted those files and preserved ownership, which lead to
those files being writable by a non-root user.

Affected Go tarballs:

  * Go for macOS version 1.4.3 through 1.21rc3, inclusive.
  * go1.4-bootstrap-20170518.tar.gz
  * go1.4-bootstrap-20170531.tar.gz

This switches to extracting the tarballs with `tar`’s `--no-same-owner`
and `--no-same-permissions` flags.
repository: repo_url: https://github.com/danielparks/puppet-golang
repo_name: danielparks/puppet-golang
description: Simple yet flexible Go installations with Puppet
repo_language: Ruby
owner: danielparks
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -4,6 +4,38 @@ All notable changes to this project will be documented in this file.
 
 ## main branch
 
+### Security fix
+
+Certain Go tarballs (see below) had files owned by non-root users:
+
+    ❯ curl -SsL https://go.dev/dl/go1.20.14.darwin-amd64.tar.gz | tar -tzvf - | head -3
+    drwxr-xr-x  0 0      0           0 Feb  2 10:19 go/
+    -rw-r--r--  0 gopher wheel    1339 Feb  2 10:09 go/CONTRIBUTING.md
+    -rw-r--r--  0 gopher wheel    1479 Feb  2 10:09 go/LICENSE
+
+In this case, the non-root user in question mapped to the first user created on
+the macOS system (UID 501).
+
+When running as root, previous versions of dp-golang would preserve file
+ownership when extracting the tarball, even if `owner` was set to something
+else. **This meant that files, such as the `go` binary, ended up being writable
+by a non-root user.**
+
+This version of dp-golang enables [`tar`]’s `--no-same-owner` and
+`--no-same-permissions` flags, which cause files to be extracted as the user
+running Puppet, or as the user/group specified in the Puppet code.
+
+GitHub security advisory: [GHSA-8h8m-h98f-vv84]
+
+#### Affected Go tarballs
+
+  * Go for macOS version 1.4.3 through 1.21rc3, inclusive.
+  * go1.4-bootstrap-20170518.tar.gz
+  * go1.4-bootstrap-20170531.tar.gz
+
+[`tar`]: https://www.man7.org/linux/man-pages/man1/tar.1.html
+[GHSA-8h8m-h98f-vv84]: https://github.com/danielparks/puppet-golang/security/advisories/GHSA-8h8m-h98f-vv84
+
 ## Release 1.2.6
 
 * Synced with [PDK][].

programming_language: Markdown
filename: from_tarball.pp
change_type: ModificationType.MODIFY
diff: @@ -109,7 +109,7 @@ define golang::from_tarball (
       ensure        => present,
       extract       => true,
       extract_path  => $go_dir,
-      extract_flags => '--strip-components 1 -xf',
+      extract_flags => '--strip-components 1 --no-same-owner --no-same-permissions -xf',
       user          => $owner,
       group         => $group,
       source        => $source,

programming_language: Perl
repo_url: https://github.com/danielparks/puppet-golang
commit_details: repo_url: https://github.com/danielparks/puppet-golang
author: Daniel Parks
author_date: 2024-02-29 00:07:53-08:00
msg: Enforce owner/group of Go installations

This now recreates a Go installation if any of its files or directories
have the wrong owner or group.
repository: repo_url: https://github.com/danielparks/puppet-golang
repo_name: danielparks/puppet-golang
description: Simple yet flexible Go installations with Puppet
repo_language: Ruby
owner: danielparks
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -36,6 +36,13 @@ GitHub security advisory: [GHSA-8h8m-h98f-vv84]
 [`tar`]: https://www.man7.org/linux/man-pages/man1/tar.1.html
 [GHSA-8h8m-h98f-vv84]: https://github.com/danielparks/puppet-golang/security/advisories/GHSA-8h8m-h98f-vv84
 
+### Changes
+
+As part of the security fix mentioned above, it became necessary to be more
+agressive about ensuring that the owner and group of files in the installation
+are correct. dp-golang now deletes and recreates any Go installation it finds
+that has a file or directory with the wrong owner or group.
+
 ## Release 1.2.6
 
 * Synced with [PDK][].

programming_language: Markdown
filename: from_tarball.pp
change_type: ModificationType.MODIFY
diff: @@ -46,6 +46,9 @@ define golang::from_tarball (
   String[1]                          $mode       = '0755',
   Stdlib::Unixpath                   $state_file = golang::state_file($go_dir),
 ) {
+  $encoded_go_dir = $go_dir.regsubst('/', '_', 'G')
+  $archive_path = "/tmp/puppet-golang${encoded_go_dir}.tar.gz"
+
   if $ensure != any_version {
     # Used to ensure that the installation is updated when $source changes.
     $file_ensure = $ensure ? {
@@ -70,6 +73,38 @@ define golang::from_tarball (
     }
   }
 
+  if $ensure == present or $ensure == any_version {
+    # Remove Go installation if any of its files have the wrong user or group.
+    # This will cause it to be replaced with a fresh installation.
+    exec { "dp/golang check ownership of ${go_dir}":
+      command     => ['rm', '-rf', $go_dir],
+      environment => [
+        "GO_DIR=${go_dir}",
+        "OWNER=${owner}",
+        "GROUP=${group}",
+      ],
+      path        => ['/usr/local/bin', '/usr/bin', '/bin'],
+      onlyif      => 'find "$GO_DIR" "(" "(" -not -user "$OWNER" ")" -or "(" -not -group "$GROUP" ")" ")" -print -quit | grep .',
+      before      => File[$go_dir],
+      notify      => Archive[$archive_path],
+    }
+  }
+
+  # File[$state_file] changing should only trigger an update when ensure is
+  # present, and not any_version.
+  if $ensure == present {
+    # If the $go_dir/bin directory exists, archive won't update it. Also, we
+    # want to remove any files that are not present in the new version.
+    exec { "dp/golang refresh go installation at ${go_dir}":
+      command     => ['rm', '-rf', $go_dir],
+      path        => ['/usr/local/bin', '/usr/bin', '/bin'],
+      refreshonly => true,
+      subscribe   => File[$state_file],
+      before      => File[$go_dir],
+      notify      => Archive[$archive_path],
+    }
+  }
+
   $directory_ensure = $ensure ? {
     'present'     => directory,
     'any_version' => directory,
@@ -85,24 +120,6 @@ define golang::from_tarball (
   }
 
   if $ensure == present or $ensure == any_version {
-    $encoded_go_dir = $go_dir.regsubst('/', '_', 'G')
-    $archive_path = "/tmp/puppet-golang${encoded_go_dir}.tar.gz"
-
-    # Only trigger an update when ensure is present, and not any_version.
-    if $ensure == present {
-      # If the $go_dir/bin directory exists, archive won't update it. Also, we
-      # want to remove any files that are not present in the new version.
-      exec { "dp/golang refresh go installation at ${go_dir}":
-        command     => ['rm', '-rf', $go_dir],
-        path        => ['/usr/local/bin', '/usr/bin', '/bin'],
-        user        => $facts['identity']['user'],
-        refreshonly => true,
-        subscribe   => File[$state_file],
-        before      => File[$go_dir],
-        notify      => Archive[$archive_path],
-      }
-    }
-
     include archive
 
     archive { $archive_path:

programming_language: Perl
filename: golang_from_tarball_spec.rb
change_type: ModificationType.MODIFY
diff: @@ -185,6 +185,48 @@ describe 'defined type golang::from_tarball' do
         its(:owner) { is_expected.to eq 'user' }
         its(:group) { is_expected.to eq 'user' }
       end
+
+      context 'enforcing file ownership' do
+        it 'chowns bin/go' do
+          File.chown(0, 0, "#{home}/user/go-install/bin/go")
+        end
+
+        it 'reinstalls Go' do
+          apply_manifest(<<~"PUPPET", expect_changes: true)
+            golang::from_tarball { '#{home}/user/go-install':
+              source => '#{source_url}',
+              owner  => 'user',
+              group  => 'user',
+              mode   => '0700',
+            }
+          PUPPET
+        end
+
+        describe file("#{home}/user/go-install") do
+          it { is_expected.to be_directory }
+          its(:mode) { is_expected.to eq '700' }
+          its(:owner) { is_expected.to eq 'user' }
+          its(:group) { is_expected.to eq 'user' }
+        end
+
+        describe file("#{home}/user/go-install/bin/go") do
+          it { is_expected.to be_file }
+          its(:mode) { is_expected.to eq '755' }
+          its(:owner) { is_expected.to eq 'user' }
+          its(:group) { is_expected.to eq 'user' }
+        end
+
+        it 'does nothing' do
+          apply_manifest(<<~"PUPPET", catch_changes: true)
+            golang::from_tarball { '#{home}/user/go-install':
+              source => '#{source_url}',
+              owner  => 'user',
+              group  => 'user',
+              mode   => '0700',
+            }
+          PUPPET
+        end
+      end
     end
 
     context 'cleans up' do

programming_language: Ruby