ArcCheck.pm revision 1.41
1# ex:ts=8 sw=4: 2# $OpenBSD: ArcCheck.pm,v 1.41 2023/05/27 10:00:48 espie Exp $ 3# 4# Copyright (c) 2005-2006 Marc Espie <espie@openbsd.org> 5# 6# Permission to use, copy, modify, and distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18# Supplementary code to handle archives in the package context. 19# Ustar allows about anything, but we want to forbid a lot of things. 20# this code is used during creation and extraction, "bridging" the gap 21# between tar balls OpenBSD::Ustar::Object and 22# packing list OpenBSD::PackingElement 23 24# specifically, during create time: 25# $o = $archive->prepare_long($item); 26# if (!$o->verify_modes($self)) 27# error... 28# if (!$o->is_allowed) 29# error... 30# - prevent a lot of weird objects from entering the archives 31# - make sure all relevant users/modes are recorded in the PLIST item 32 33# during extraction: 34# $o->validate_meta($item) or 35# error... 36 37use strict; 38use warnings; 39 40use OpenBSD::Ustar; 41 42package OpenBSD::Ustar::Object; 43use POSIX; 44 45sub is_allowed() { 0 } 46 47# match archive header link name against actual link name 48sub _check_linkname 49{ 50 my ($self, $linkname) = @_; 51 my $c = $self->{linkname}; 52 if ($self->isHardLink && defined $self->{cwd}) { 53 $c = $self->{cwd}.'/'.$c; 54 } 55 return $c eq $linkname; 56} 57 58sub _errsay 59{ 60 my ($self, @args) = @_; 61 $self->{archive}{state}->errsay(@args); 62} 63 64sub validate_meta 65{ 66 my ($o, $item) = @_; 67 68 $o->{cwd} = $item->cwd; 69 if (defined $item->{symlink} || $o->isSymLink) { 70 if (!defined $item->{symlink}) { 71 $o->_errsay("bogus symlink #1 -> #2", 72 $item->name, $o->{linkname}); 73 $o->_errsay("\t(no \@symlink annotation in packing-list)"); 74 return 0; 75 } 76 if (!$o->isSymLink) { 77 $o->_errsay("bogus symlink #1 -> #2", 78 $item->name, $item->{symlink}); 79 $o->_errsay("\t(not a symlink in the tarball)"); 80 return 0; 81 } 82 if (!$o->_check_linkname($item->{symlink})) { 83 $o->_errsay("archive symlink does not match #1 != #2", 84 $o->{linkname}, $item->{symlink}); 85 return 0; 86 } 87 } elsif (defined $item->{link} || $o->isHardLink) { 88 if (!defined $item->{link}) { 89 $o->_errsay("bogus hardlink #1 -> #2", 90 $item->name, $o->{linkname}); 91 $o->_errsay("\t(no \@link annotation in packing-list)"); 92 return 0; 93 } 94 if (!$o->isHardLink) { 95 $o->_errsay("bogus hardlink #1 -> #2", 96 $item->name, $item->{link}); 97 $o->_errsay("\t(not a link in the tarball)"); 98 return 0; 99 } 100 if (!$o->_check_linkname($item->{link})) { 101 $o->_errsay("archive hardlink does not match #1 != #2", 102 $o->{linkname}, $item->{link}); 103 return 0; 104 } 105 } elsif ($o->isFile) { 106 if (!defined $item->{size}) { 107 $o->_errsay("Error: file #1 does not have recorded size", 108 $item->fullname); 109 return 0; 110 } elsif ($item->{size} != $o->{size}) { 111 $o->_errsay("Error: size does not match for #1", 112 $item->fullname); 113 return 0; 114 } 115 } else { 116 $o->_errsay("archive content for #1 should be file", 117 $item->name); 118 return 0; 119 } 120 return $o->verify_modes($item); 121} 122 123sub _strip_modes 124{ 125 my ($o, $item) = @_; 126 127 my $result = $o->{mode}; 128 129 # disallow writable files/dirs without explicit annotation 130 if (!defined $item->{mode}) { 131 # if there's an owner, we have to be explicit 132 if (defined $item->{owner}) { 133 $result &= ~(S_IWUSR|S_IWGRP|S_IWOTH); 134 } else { 135 $result &= ~(S_IWGRP|S_IWOTH); 136 } 137 # and make libraries non-executable 138 if ($item->is_a_library) { 139 $result &= ~(S_IXUSR|S_IXGRP|S_IXOTH); 140 } 141 $result |= S_IROTH | S_IRGRP; 142 } 143 # XXX newer tarballs never have suid/sgid, but some 7.0 packages 144 # still have them... We'll strip them here unconditionally instead 145 # of in XXX /1 and XXX /2 146 if (defined $item->{group} || defined $item->{owner}) { 147 $result &= ~(S_ISUID|S_ISGID); 148 } 149 return $result; 150} 151 152sub _printable_mode 153{ 154 my $o = shift; 155 return sprintf("%4o", 156 $o->{mode} & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID)); 157} 158 159sub verify_modes 160{ 161 my ($o, $item) = @_; 162 my $result = 1; 163 164 if (!defined $item->{owner}) { 165 if ($o->{uname} ne 'root') { 166 $o->_errsay("Error: no \@owner for #1 (#2)", 167 $item->fullname, $o->{uname}); 168 $result = 0; 169 } 170 } 171 if (!defined $item->{group}) { 172 if ($o->{gname} ne 'bin' && $o->{gname} ne 'wheel') { 173 $o->_errsay("Error: no \@group for #1 (#2)", 174 $item->fullname, $o->{gname}); 175 $result = 0; 176 } 177 } 178 # XXX /1 179 $o->{mode} &= ~(S_ISUID|S_ISGID); 180 if ($o->{mode} != $o->_strip_modes($o)) { 181 $o->_errsay("Error: weird mode for #1: #2", $item->fullname, 182 $o->_printable_mode); 183 $result = 0; 184 } 185 return $result; 186} 187 188package OpenBSD::Ustar::HardLink; 189sub is_allowed() { 1 } 190 191package OpenBSD::Ustar::SoftLink; 192sub is_allowed() { 1 } 193 194package OpenBSD::Ustar::File; 195sub is_allowed() { 1 } 196 197package OpenBSD::Ustar; 198use POSIX; 199 200# prepare item according to pkg_create's rules. 201sub prepare_long 202{ 203 my ($self, $item) = @_; 204 my $entry; 205 if (defined $item->{wtempname}) { 206 $entry = $self->prepare($item->{wtempname}, ''); 207 } else { 208 $entry = $self->prepare($item->name); 209 } 210 if (defined $item->{owner}) { 211 $entry->{uname} = $item->{owner}; 212 if (defined $item->{uid}) { 213 $entry->{uid} = $item->{uid}; 214 } else { 215 delete $entry->{uid}; 216 } 217 } else { 218 $entry->{uname} = "root"; 219 delete $entry->{uid}; 220 } 221 if (defined $item->{group}) { 222 $entry->{gname} = $item->{group}; 223 if (defined $item->{gid}) { 224 $entry->{gid} = $item->{gid}; 225 } else { 226 delete $entry->{gid}; 227 } 228 } else { 229 $entry->{gname} = "bin"; 230 delete $entry->{gid}; 231 } 232 # likewise, we skip links on extractions, so hey, don't even care 233 # about modes and stuff. 234 if ($entry->isSymLink) { 235 $entry->{mode} = 0777; 236 $entry->{uname} = 'root'; 237 $entry->{gname} = 'wheel'; 238 delete $entry->{uid}; 239 delete $entry->{gid}; 240 } 241 $entry->recheck_owner; 242 if (!defined $entry->{uname}) { 243 $self->fatal("No user name for #1 (uid #2)", 244 $item->name, $entry->{uid}); 245 } 246 if (!defined $entry->{gname}) { 247 $self->fatal("No group name for #1 (gid #2)", 248 $item->name, $entry->{gid}); 249 } 250 # XXX /2 251 $entry->{mode} = $entry->_strip_modes($item) & ~(S_ISUID|S_ISGID); 252 if (defined $item->{ts}) { 253 delete $entry->{mtime}; 254 } 255 256 $entry->set_name($item->name); 257 return $entry; 258} 259 2601; 261