ArcCheck.pm revision 1.39
1# ex:ts=8 sw=4:
2# $OpenBSD: ArcCheck.pm,v 1.39 2023/05/16 16:45:04 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
31# during extraction:
32#	$o->validate_meta($item) or
33#		error...
34
35use strict;
36use warnings;
37
38use OpenBSD::Ustar;
39
40package OpenBSD::Ustar::Object;
41use POSIX;
42
43sub is_allowed() { 0 }
44
45# match archive header link name against actual link name
46sub check_linkname
47{
48	my ($self, $linkname) = @_;
49	my $c = $self->{linkname};
50	if ($self->isHardLink && defined $self->{cwd}) {
51		$c = $self->{cwd}.'/'.$c;
52	}
53	return $c eq $linkname;
54}
55
56sub errsay
57{
58	my ($self, @args) = @_;
59	$self->{archive}{state}->errsay(@args);
60}
61
62sub validate_meta
63{
64	my ($o, $item) = @_;
65
66	$o->{cwd} = $item->cwd;
67	if (defined $item->{symlink} || $o->isSymLink) {
68		if (!defined $item->{symlink}) {
69			$o->errsay("bogus symlink #1 -> #2",
70			    $item->name, $o->{linkname});
71			$o->errsay("\t(no \@symlink annotation in packing-list)");
72			return 0;
73		}
74		if (!$o->isSymLink) {
75			$o->errsay("bogus symlink #1 -> #2",
76			    $item->name, $item->{symlink});
77			$o->errsay("\t(not a symlink in the tarball)");
78			return 0;
79		}
80		if (!$o->check_linkname($item->{symlink})) {
81			$o->errsay("archive symlink does not match #1 != #2",
82			    $o->{linkname}, $item->{symlink});
83			return 0;
84		}
85	} elsif (defined $item->{link} || $o->isHardLink) {
86		if (!defined $item->{link}) {
87			$o->errsay("bogus hardlink #1 -> #2",
88			    $item->name, $o->{linkname});
89			$o->errsay("\t(no \@link annotation in packing-list)");
90			return 0;
91		}
92		if (!$o->isHardLink) {
93			$o->errsay("bogus hardlink #1 -> #2",
94			    $item->name, $item->{link});
95			$o->errsay("\t(not a link in the tarball)");
96			return 0;
97		}
98		if (!$o->check_linkname($item->{link})) {
99			$o->errsay("archive hardlink does not match #1 != #2",
100			    $o->{linkname}, $item->{link});
101			return 0;
102		}
103	} elsif ($o->isFile) {
104		if (!defined $item->{size}) {
105			$o->errsay("Error: file #1 does not have recorded size",
106			    $item->fullname);
107			return 0;
108		} elsif ($item->{size} != $o->{size}) {
109			$o->errsay("Error: size does not match for #1",
110			    $item->fullname);
111			return 0;
112		}
113	} else {
114		$o->errsay("archive content for #1 should be file",
115		    $item->name);
116		return 0;
117	}
118	return $o->verify_modes($item);
119}
120
121sub strip_modes
122{
123	my ($o, $item) = @_;
124
125	my $result = $o->{mode};
126
127	# disallow writable files/dirs without explicit annotation
128	if (!defined $item->{mode}) {
129		# if there's an owner, we have to be explicit
130		if (defined $item->{owner}) {
131			$result &= ~(S_IWUSR|S_IWGRP|S_IWOTH);
132		} else {
133			$result &= ~(S_IWGRP|S_IWOTH);
134		}
135		# and make libraries non-executable
136		if ($item->is_a_library) {
137			$result &= ~(S_IXUSR|S_IXGRP|S_IXOTH);
138		}
139		$result |= S_IROTH | S_IRGRP;
140	}
141	# XXX newer tarballs never have suid/sgid, but some 7.0 packages
142	# still have them... We'll strip them here unconditionally instead
143	# of in XXX /1 and XXX /2
144	if (defined $item->{group} || defined $item->{owner}) {
145		$result &= ~(S_ISUID|S_ISGID);
146	}
147	return $result;
148}
149
150sub printable_mode
151{
152	my $o = shift;
153	return sprintf("%4o",
154	    $o->{mode} & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID));
155}
156
157sub verify_modes
158{
159	my ($o, $item) = @_;
160	my $result = 1;
161
162	if (!defined $item->{owner}) {
163		if ($o->{uname} ne 'root') {
164			$o->errsay("Error: no \@owner for #1 (#2)",
165			    $item->fullname, $o->{uname});
166	    		$result = 0;
167		}
168	}
169	if (!defined $item->{group}) {
170		if ($o->{gname} ne 'bin' && $o->{gname} ne 'wheel') {
171			$o->errsay("Error: no \@group for #1 (#2)",
172			    $item->fullname, $o->{gname});
173			$result = 0;
174		}
175	}
176	# XXX /1
177	$o->{mode} &= ~(S_ISUID|S_ISGID);
178	if ($o->{mode} != $o->strip_modes($o)) {
179		$o->errsay("Error: weird mode for #1: #2", $item->fullname,
180		    $o->printable_mode);
181		    $result = 0;
182 	}
183	return $result;
184}
185
186package OpenBSD::Ustar::HardLink;
187sub is_allowed() { 1 }
188
189package OpenBSD::Ustar::SoftLink;
190sub is_allowed() { 1 }
191
192package OpenBSD::Ustar::File;
193sub is_allowed() { 1 }
194
195package OpenBSD::Ustar;
196use POSIX;
197
198# prepare item according to pkg_create's rules.
199sub prepare_long
200{
201	my ($self, $item) = @_;
202	my $entry;
203	if (defined $item->{wtempname}) {
204		$entry = $self->prepare($item->{wtempname}, '');
205	} else {
206		$entry = $self->prepare($item->name);
207	}
208	if (defined $item->{owner}) {
209		$entry->{uname} = $item->{owner};
210		if (defined $item->{uid}) {
211			$entry->{uid} = $item->{uid};
212		} else {
213			delete $entry->{uid};
214		}
215	} else {
216		$entry->{uname} = "root";
217		delete $entry->{uid};
218	}
219	if (defined $item->{group}) {
220		$entry->{gname} = $item->{group};
221		if (defined $item->{gid}) {
222			$entry->{gid} = $item->{gid};
223		} else {
224			delete $entry->{gid};
225		}
226	} else {
227		$entry->{gname} = "bin";
228		delete $entry->{gid};
229	}
230	# likewise, we skip links on extractions, so hey, don't even care
231	# about modes and stuff.
232	if ($entry->isSymLink) {
233		$entry->{mode} = 0777;
234		$entry->{uname} = 'root';
235		$entry->{gname} = 'wheel';
236		delete $entry->{uid};
237		delete $entry->{gid};
238	}
239	$entry->recheck_owner;
240	if (!defined $entry->{uname}) {
241		$self->fatal("No user name for #1 (uid #2)",
242		    $item->name, $entry->{uid});
243	}
244	if (!defined $entry->{gname}) {
245		$self->fatal("No group name for #1 (gid #2)",
246		    $item->name, $entry->{gid});
247	}
248	# XXX /2
249	$entry->{mode} = $entry->strip_modes($item) & ~(S_ISUID|S_ISGID);
250	if (defined $item->{ts}) {
251		delete $entry->{mtime};
252	}
253
254	$entry->set_name($item->name);
255	return $entry;
256}
257
2581;
259