]> ruderich.org/simon Gitweb - blhc/blhc.git/blobdiff - bin/blhc
Release 0.03.
[blhc/blhc.git] / bin / blhc
index b57f470ba1de315dfd2687566e784b0b1af8ea2f..712d16cb7d37c7cb24f5ab5d0274304b06b46d7e 100755 (executable)
--- a/bin/blhc
+++ b/bin/blhc
@@ -24,7 +24,7 @@ use warnings;
 use Getopt::Long ();
 use Text::ParseWords ();
 
 use Getopt::Long ();
 use Text::ParseWords ();
 
-our $VERSION = '0.01';
+our $VERSION = '0.03';
 
 
 # CONSTANTS/VARIABLES
 
 
 # CONSTANTS/VARIABLES
@@ -153,8 +153,7 @@ my @def_cflags = (
 );
 my @def_cflags_format = (
     '-Wformat',
 );
 my @def_cflags_format = (
     '-Wformat',
-    '-Wformat-security',
-    '-Werror=format-security',
+    '-Werror=format-security', # implies -Wformat-security
 );
 my @def_cflags_fortify = (
     # fortify needs at least -O1, but -O2 is recommended anyway
 );
 my @def_cflags_fortify = (
     # fortify needs at least -O1, but -O2 is recommended anyway
@@ -172,7 +171,14 @@ my @def_cxxflags = (
 # @def_cxxflags_* is the same as @def_cflags_*.
 my @def_cppflags = ();
 my @def_cppflags_fortify = (
 # @def_cxxflags_* is the same as @def_cflags_*.
 my @def_cppflags = ();
 my @def_cppflags_fortify = (
-    '-D_FORTIFY_SOURCE=2',
+    '-D_FORTIFY_SOURCE=2', # must be first, see cppflags_fortify_broken()
+    # If you add another flag fix hack below (search for "Hack to fix").
+);
+my @def_cppflags_fortify_bad = (
+    # These flags may overwrite -D_FORTIFY_SOURCE=2.
+    '-U_FORTIFY_SOURCE',
+    '-D_FORTIFY_SOURCE=0',
+    '-D_FORTIFY_SOURCE=1',
 );
 my @def_ldflags = ();
 my @def_ldflags_relro = (
 );
 my @def_ldflags = ();
 my @def_ldflags_relro = (
@@ -190,7 +196,7 @@ my @def_ldflags_pic = (
     '-fpic',
     '-shared',
 );
     '-fpic',
     '-shared',
 );
-# References to all flags checked by the parser.
+# References to all flags checked by the flag checker.
 my @flag_refs = (
     \@def_cflags,
     \@def_cflags_format,
 my @flag_refs = (
     \@def_cflags,
     \@def_cflags_format,
@@ -203,11 +209,12 @@ my @flag_refs = (
     \@def_ldflags,
     \@def_ldflags_relro,
     \@def_ldflags_bindnow,
     \@def_ldflags,
     \@def_ldflags_relro,
     \@def_ldflags_bindnow,
+    \@def_ldflags_pie,
 );
 # References to all used flags.
 my @flag_refs_all = (
     @flag_refs,
 );
 # References to all used flags.
 my @flag_refs_all = (
     @flag_refs,
-    \@def_ldflags_pie,
+    \@def_cppflags_fortify_bad,
     \@def_ldflags_pic,
 );
 # Renaming rules for the output so the regex parts are not visible. Also
     \@def_ldflags_pic,
 );
 # Renaming rules for the output so the regex parts are not visible. Also
@@ -227,6 +234,14 @@ my %exit_code = (
     invalid_cmake        => 1 << 5,
 );
 
     invalid_cmake        => 1 << 5,
 );
 
+my %buildd_tag = (
+    no_compiler_commands => 'I-no-compiler-commands',
+    non_verbose_build    => 'W-compiler-flags-hidden',
+    flags_missing        => 'W-dpkg-buildflags-missing',
+    hardening_wrapper    => 'I-hardening-wrapper-used',
+    invalid_cmake        => 'I-invalid-cmake-used',
+);
+
 # Statistics of missing flags and non-verbose build commands. Used for
 # $option_buildd.
 my %statistics = (
 # Statistics of missing flags and non-verbose build commands. Used for
 # $option_buildd.
 my %statistics = (
@@ -318,6 +333,22 @@ sub all_flags_used {
     return 0;
 }
 
     return 0;
 }
 
+sub cppflags_fortify_broken {
+    my ($line, $missing_flags) = @_;
+
+    # This doesn't take the position into account, but is a simple solution.
+    # And if the build system tries to force -D_FORTIFY_SOURCE=0/1, something
+    # is wrong anyway.
+
+    if (any_flags_used($line, @def_cppflags_fortify_bad)) {
+        # $def_cppflags_fortify[0] must be -D_FORTIFY_SOURCE=2!
+        push @{$missing_flags}, $def_cppflags_fortify[0];
+        return 1;
+    }
+
+    return 0;
+}
+
 # Modifies $missing_flags_ref array.
 sub pic_pie_conflict {
     my ($line, $pie, $missing_flags_ref, @flags_pie) = @_;
 # Modifies $missing_flags_ref array.
 sub pic_pie_conflict {
     my ($line, $pie, $missing_flags_ref, @flags_pie) = @_;
@@ -389,6 +420,21 @@ sub is_non_verbose_build {
     return 1;
 }
 
     return 1;
 }
 
+sub remove_flags {
+    my ($flag_refs_ref, $flag_renames_ref, @flags) = @_;
+
+    my %removes = map { $_ => 1 } @flags;
+    foreach my $flags (@{$flag_refs_ref}) {
+        @{$flags} = grep {
+            # Flag found as string.
+            not exists $removes{$_}
+            # Flag found as string representation of regexp.
+                and (not defined $flag_renames_ref->{$_}
+                        or not exists $removes{$flag_renames_ref->{$_}})
+        } @{$flags};
+    }
+}
+
 sub compile_flag_regexp {
     my ($flag_renames_ref, @flags) = @_;
 
 sub compile_flag_regexp {
     my ($flag_renames_ref, @flags) = @_;
 
@@ -424,34 +470,37 @@ sub extension_found {
 # MAIN
 
 # Parse command line arguments.
 # MAIN
 
 # Parse command line arguments.
-my $option_help        = 0;
-my $option_version     = 0;
-my $option_pie         = 0;
-my $option_bindnow     = 0;
-my @option_ignore_arch = ();
-my @option_ignore_flag = ();
-my @option_ignore_line = ();
-my $option_all         = 0;
-my $option_arch        = undef;
-my $option_buildd      = 0;
-   $option_color       = 0;
+my $option_help             = 0;
+my $option_version          = 0;
+my $option_pie              = 0;
+my $option_bindnow          = 0;
+my @option_ignore_arch      = ();
+my @option_ignore_flag      = ();
+my @option_ignore_arch_flag = ();
+my @option_ignore_line      = ();
+my @option_ignore_arch_line = ();
+my $option_all              = 0;
+my $option_arch             = undef;
+my $option_buildd           = 0;
+   $option_color            = 0;
 if (not Getopt::Long::GetOptions(
 if (not Getopt::Long::GetOptions(
-            'help|h|?'      => \$option_help,
-            'version'       => \$option_version,
+            'help|h|?'           => \$option_help,
+            'version'            => \$option_version,
             # Hardening options.
             # Hardening options.
-            'pie'           => \$option_pie,
-            'bindnow'       => \$option_bindnow,
-            'all'           => \$option_all,
+            'pie'                => \$option_pie,
+            'bindnow'            => \$option_bindnow,
+            'all'                => \$option_all,
             # Ignore.
             # Ignore.
-            'ignore-arch=s' => \@option_ignore_arch,
-            'ignore-flag=s' => \@option_ignore_flag,
-            'ignore-line=s' => \@option_ignore_line,
+            'ignore-arch=s'      => \@option_ignore_arch,
+            'ignore-flag=s'      => \@option_ignore_flag,
+            'ignore-arch-flag=s' => \@option_ignore_arch_flag,
+            'ignore-line=s'      => \@option_ignore_line,
+            'ignore-arch-line=s' => \@option_ignore_arch_line,
             # Misc.
             # Misc.
-            'color'         => \$option_color,
-            'arch=s'        => \$option_arch,
-            'buildd'        => \$option_buildd,
-        )
-        or scalar @ARGV == 0) {
+            'color'              => \$option_color,
+            'arch=s'             => \$option_arch,
+            'buildd'             => \$option_buildd,
+        )) {
     require Pod::Usage;
     Pod::Usage::pod2usage(2);
 }
     require Pod::Usage;
     Pod::Usage::pod2usage(2);
 }
@@ -478,6 +527,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
     exit 0;
 }
 
     exit 0;
 }
 
+# Arguments missing.
+if (scalar @ARGV == 0) {
+    require Pod::Usage;
+    Pod::Usage::pod2usage(2);
+}
+
 # Don't load Term::ANSIColor in buildd mode because Term::ANSIColor is not
 # installed on Debian's buildds.
 if (not $option_buildd) {
 # Don't load Term::ANSIColor in buildd mode because Term::ANSIColor is not
 # installed on Debian's buildds.
 if (not $option_buildd) {
@@ -489,17 +544,27 @@ if ($option_all) {
     $option_bindnow = 1;
 }
 
     $option_bindnow = 1;
 }
 
+# Precompiled ignores for faster lookup.
+my %option_ignore_arch_flag = ();
+my %option_ignore_arch_line = ();
+
 # Strip flags which should be ignored.
 if (scalar @option_ignore_flag > 0) {
 # Strip flags which should be ignored.
 if (scalar @option_ignore_flag > 0) {
-    my %ignores = map { $_ => 1 } @option_ignore_flag;
-    foreach my $flags (@flag_refs) {
-        @{$flags} = grep {
-            # Flag found as string.
-            not exists $ignores{$_}
-            # Flag found as string representation of regexp.
-                and (not defined $flag_renames{$_}
-                        or not exists $ignores{$flag_renames{$_}})
-            } @{$flags};
+    remove_flags(\@flag_refs, \%flag_renames, @option_ignore_flag);
+}
+# Same for arch specific ignore flags, but only prepare here.
+if (scalar @option_ignore_arch_flag > 0) {
+    foreach my $ignore (@option_ignore_arch_flag) {
+        my ($ignore_arch, $ignore_flag) = split ':', $ignore, 2;
+
+        if (not $ignore_arch or not $ignore_flag) {
+            printf STDERR 'Value "%s" invalid for option ignore-arch-flag '
+                        . '("arch:flag" expected)' . "\n", $ignore;
+            require Pod::Usage;
+            Pod::Usage::pod2usage(2);
+        }
+
+        push @{$option_ignore_arch_flag{$ignore_arch}}, $ignore_flag;
     }
 }
 
     }
 }
 
@@ -513,6 +578,21 @@ foreach my $flags (@flag_refs_all) {
 foreach my $ignore (@option_ignore_line) {
     $ignore = qr/^$ignore$/;
 }
 foreach my $ignore (@option_ignore_line) {
     $ignore = qr/^$ignore$/;
 }
+# Same for arch specific ignore lines.
+if (scalar @option_ignore_arch_line > 0) {
+    foreach my $ignore (@option_ignore_arch_line) {
+        my ($ignore_arch, $ignore_line) = split ':', $ignore, 2;
+
+        if (not $ignore_arch or not $ignore_line) {
+            printf STDERR 'Value "%s" invalid for option ignore-arch-line '
+                        . '("arch:line" expected)' . "\n", $ignore;
+            require Pod::Usage;
+            Pod::Usage::pod2usage(2);
+        }
+
+        push @{$option_ignore_arch_line{$ignore_arch}}, qr/^$ignore_line$/;
+    }
+}
 
 # Final exit code.
 my $exit = 0;
 
 # Final exit code.
 my $exit = 0;
@@ -521,7 +601,7 @@ FILE:
 foreach my $file (@ARGV) {
     print "checking '$file'...\n" if scalar @ARGV > 1;
 
 foreach my $file (@ARGV) {
     print "checking '$file'...\n" if scalar @ARGV > 1;
 
-    open my $fh, '<', $file or die "$!: $file";
+    open my $fh, '<', $file or die $!;
 
     # Architecture of this file.
     my $arch = $option_arch;
 
     # Architecture of this file.
     my $arch = $option_arch;
@@ -576,7 +656,7 @@ foreach my $file (@ARGV) {
             if (not $option_buildd) {
                 error_invalid_cmake($1);
             } else {
             if (not $option_buildd) {
                 error_invalid_cmake($1);
             } else {
-                print "W-invalid-cmake-used $1\n";
+                print "$buildd_tag{invalid_cmake} $1\n";
             }
             $exit |= $exit_code{invalid_cmake};
         }
             }
             $exit |= $exit_code{invalid_cmake};
         }
@@ -588,7 +668,7 @@ foreach my $file (@ARGV) {
             if (not $option_buildd) {
                 error_hardening_wrapper();
             } else {
             if (not $option_buildd) {
                 error_hardening_wrapper();
             } else {
-                print "I-hardening-wrapper-used\n";
+                print "$buildd_tag{hardening_wrapper}\n";
             }
             $exit |= $exit_code{hardening_wrapper};
             next FILE;
             }
             $exit |= $exit_code{hardening_wrapper};
             next FILE;
@@ -688,10 +768,18 @@ foreach my $file (@ARGV) {
                                 [Cc]ompiler[\s.]*:?\s+
                                 /xo;
             next if $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex_full\s*$/o;
                                 [Cc]ompiler[\s.]*:?\s+
                                 /xo;
             next if $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex_full\s*$/o;
+            # `moc-qt4`, contains '-I/usr/share/qt4/mkspecs/linux-g++' (or
+            # similar for other architectures) which gets recognized as a
+            # compiler line. Ignore it.
+            next if $line =~ m{^/usr/bin/moc-qt4
+                               \s.+\s
+                               -I/usr/share/qt4/mkspecs/[a-z]+-g\++(?:-64)?
+                               \s}x;
 
             # Check if additional hardening options were used. Used to ensure
             # they are used for the complete build.
 
             # Check if additional hardening options were used. Used to ensure
             # they are used for the complete build.
-            $harden_pie     = 1 if any_flags_used($line, @def_cflags_pie, @def_ldflags_pie);
+            $harden_pie     = 1 if any_flags_used($line, @def_cflags_pie,
+                                                         @def_ldflags_pie);
             $harden_bindnow = 1 if any_flags_used($line, @def_ldflags_bindnow);
 
             push @input, $line;
             $harden_bindnow = 1 if any_flags_used($line, @def_ldflags_bindnow);
 
             push @input, $line;
@@ -714,7 +802,7 @@ foreach my $file (@ARGV) {
         if (not $option_buildd) {
             print "No compiler commands!\n";
         } else {
         if (not $option_buildd) {
             print "No compiler commands!\n";
         } else {
-            print "W-no-compiler-commands\n";
+            print "$buildd_tag{no_compiler_commands}\n";
         }
         $exit |= $exit_code{no_compiler_commands};
         next FILE;
         }
         $exit |= $exit_code{no_compiler_commands};
         next FILE;
@@ -776,12 +864,34 @@ foreach my $file (@ARGV) {
         @ldflags = (@ldflags, @def_ldflags_bindnow);
     }
 
         @ldflags = (@ldflags, @def_ldflags_bindnow);
     }
 
+    # Hack to fix cppflags_fortify_broken() if --ignore-flag
+    # -D_FORTIFY_SOURCE=2 is used to ignore missing fortification. Only works
+    # as long as @def_cppflags_fortify contains only one variable.
+    if (scalar @def_cppflags_fortify == 0) {
+        $harden_fortify = 0;
+    }
+
+    # Ignore flags for this arch if requested.
+    if ($arch and exists $option_ignore_arch_flag{$arch}) {
+        my @flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
+
+        remove_flags(\@flag_refs,
+                     \%flag_renames,
+                     @{$option_ignore_arch_flag{$arch}});
+    }
+
+    my @ignore_line = @option_ignore_line;
+    # Ignore lines for this arch if requested.
+    if ($arch and exists $option_ignore_arch_line{$arch}) {
+        @ignore_line = (@ignore_line, @{$option_ignore_arch_line{$arch}});
+    }
+
 LINE:
     for (my $i = 0; $i < scalar @input; $i++) {
         my $line = $input[$i];
 
         # Ignore line if requested.
 LINE:
     for (my $i = 0; $i < scalar @input; $i++) {
         my $line = $input[$i];
 
         # Ignore line if requested.
-        foreach my $ignore (@option_ignore_line) {
+        foreach my $ignore (@ignore_line) {
             next LINE if $line =~ /$ignore/;
         }
 
             next LINE if $line =~ /$ignore/;
         }
 
@@ -911,7 +1021,11 @@ LINE:
             }
             $exit |= $exit_code{flags_missing};
         }
             }
             $exit |= $exit_code{flags_missing};
         }
-        if ($preprocess and not all_flags_used($line, \@missing, @cppflags)
+        if ($preprocess
+                and (not all_flags_used($line, \@missing, @cppflags)
+                    # The fortify flag might be overwritten, detect that.
+                     or ($harden_fortify
+                         and cppflags_fortify_broken($line, \@missing)))
                 # Assume dpkg-buildflags returns the correct flags.
                 and index($line, '`dpkg-buildflags --get CPPFLAGS`') == -1) {
             if (not $option_buildd) {
                 # Assume dpkg-buildflags returns the correct flags.
                 and index($line, '`dpkg-buildflags --get CPPFLAGS`') == -1) {
             if (not $option_buildd) {
@@ -962,11 +1076,11 @@ if ($option_buildd) {
     }
     if (scalar @warning) {
         local $" = ', '; # array join string
     }
     if (scalar @warning) {
         local $" = ', '; # array join string
-        print "W-dpkg-buildflags-missing @warning missing\n";
+        print "$buildd_tag{flags_missing} @warning missing\n";
     }
 
     if ($statistics{commands_nonverbose}) {
     }
 
     if ($statistics{commands_nonverbose}) {
-        printf "W-compiler-flags-hidden %d (of %d) hidden\n",
+        printf "$buildd_tag{non_verbose_build} %d (of %d) hidden\n",
                $statistics{commands_nonverbose},
                $statistics{commands},
     }
                $statistics{commands_nonverbose},
                $statistics{commands},
     }
@@ -984,7 +1098,7 @@ blhc - build log hardening check, checks build logs for missing hardening flags
 
 =head1 SYNOPSIS
 
 
 =head1 SYNOPSIS
 
-B<blhc> [I<options>] I<E<lt>dpkg-buildpackage build log fileE<gt>..>
+B<blhc> [I<options>] I<< <dpkg-buildpackage build log file>.. >>
 
 =head1 DESCRIPTION
 
 
 =head1 DESCRIPTION
 
@@ -995,6 +1109,8 @@ It's designed to check build logs generated by Debian's dpkg-buildpackage (or
 tools using dpkg-buildpackage like pbuilder or the official buildd build logs)
 to help maintainers detect missing hardening flags in their packages.
 
 tools using dpkg-buildpackage like pbuilder or the official buildd build logs)
 to help maintainers detect missing hardening flags in their packages.
 
+If there's no output, no flags are missing and the build log is fine.
+
 =head1 OPTIONS
 
 =over 8
 =head1 OPTIONS
 
 =over 8
@@ -1021,17 +1137,17 @@ changes are in effect:
 
 =over 2
 
 
 =over 2
 
-=item
+=item *
 
 Print tags instead of normal warnings, see L</"BUILDD TAGS"> for a list of
 possible tags.
 
 
 Print tags instead of normal warnings, see L</"BUILDD TAGS"> for a list of
 possible tags.
 
-=item
+=item *
 
 Don't check hardening flags in old log files (if dpkg-dev << 1.16.1 is
 detected).
 
 
 Don't check hardening flags in old log files (if dpkg-dev << 1.16.1 is
 detected).
 
-=item
+=item *
 
 Don't require Term::ANSIColor.
 
 
 Don't require Term::ANSIColor.
 
@@ -1047,6 +1163,14 @@ Ignore build logs from architectures matching I<arch>. I<arch> is a string.
 
 Used to prevent false positives. This option can be specified multiple times.
 
 
 Used to prevent false positives. This option can be specified multiple times.
 
+=item B<--ignore-arch-flag> I<arch>:I<flag>
+
+Like B<--ignore-flag>, but only ignore flag on I<arch>.
+
+=item B<--ignore-arch-line> I<arch>:I<line>
+
+Like B<--ignore-line>, but only ignore line on I<arch>.
+
 =item B<--ignore-flag> I<flag>
 
 Don't print an error when the specific flag is missing in a compiler line.
 =item B<--ignore-flag> I<flag>
 
 Don't print an error when the specific flag is missing in a compiler line.
@@ -1088,6 +1212,8 @@ Normal usage, parse a single log file.
 
     blhc path/to/log/file
 
 
     blhc path/to/log/file
 
+If there's no output, no flags are missing and the build log is fine.
+
 Parse multiple log files. The exit code is ORed over all files.
 
     blhc path/to/directory/with/log/files/*
 Parse multiple log files. The exit code is ORed over all files.
 
     blhc path/to/directory/with/log/files/*
@@ -1096,6 +1222,10 @@ Don't treat missing C<-g> as error:
 
     blhc --ignore-flag -g path/to/log/file
 
 
     blhc --ignore-flag -g path/to/log/file
 
+Don't treat missing C<-pie> on kfreebsd-amd64 as error:
+
+    blhc --ignore-arch-flag kfreebsd-amd64:-pie path/to/log/file
+
 Ignore lines consisting exactly of C<./script gcc file> which would cause a
 false positive.
 
 Ignore lines consisting exactly of C<./script gcc file> which would cause a
 false positive.
 
@@ -1117,17 +1247,13 @@ which is displayed.
 
 =over 2
 
 
 =over 2
 
-=item
-
-B<I-hardening-wrapper-used>
+=item B<I-hardening-wrapper-used>
 
 The package uses hardening-wrapper which intercepts calls to gcc and adds
 hardening flags. The build log doesn't contain any hardening flags and thus
 can't be checked by blhc.
 
 
 The package uses hardening-wrapper which intercepts calls to gcc and adds
 hardening flags. The build log doesn't contain any hardening flags and thus
 can't be checked by blhc.
 
-=item
-
-B<W-compiler-flags-hidden> (summary of hidden lines)
+=item B<W-compiler-flags-hidden> (summary of hidden lines)
 
 Build log contains lines which hide the real compiler flags. For example:
 
 
 Build log contains lines which hide the real compiler flags. For example:
 
@@ -1141,19 +1267,20 @@ F<debian/rules> fixes builds with hidden compiler flags. Sometimes C<.SILENT>
 in a F<Makefile> must be removed. And as last resort the F<Makefile> must be
 patched to remove the C<@>s hiding the real compiler commands.
 
 in a F<Makefile> must be removed. And as last resort the F<Makefile> must be
 patched to remove the C<@>s hiding the real compiler commands.
 
-=item
-
-B<W-dpkg-buildflags-missing> (summary of missing flags)
+=item B<W-dpkg-buildflags-missing> (summary of missing flags)
 
 CPPFLAGS, CFLAGS, CXXFLAGS, LDFLAGS missing.
 
 
 CPPFLAGS, CFLAGS, CXXFLAGS, LDFLAGS missing.
 
-=item
+=item B<I-invalid-cmake-used> (version)
 
 
-B<W-invalid-cmake-used> (version)
+By default CMake ignores CPPFLAGS thus missing those hardening flags. Debian
+patched CMake in versions 2.8.7-1 and 2.8.7-2 to respect CPPFLAGS, but this
+patch was rejected by upstream and later reverted in Debian. Thus those two
+versions show correct usage of CPPFLAGS even if the package doesn't correctly
+handle them (for example by passing them to CFLAGS). To prevent false
+negatives just blacklist those two versions.
 
 
-=item
-
-B<W-no-compiler-commands>
+=item B<I-no-compiler-commands>
 
 No compiler commands were detected. Either the log contains none or they were
 not correctly detected by blhc (please report the bug in this case).
 
 No compiler commands were detected. Either the log contains none or they were
 not correctly detected by blhc (please report the bug in this case).
@@ -1191,6 +1318,11 @@ Missing hardening flags.
 
 Hardening wrapper detected, no tests performed.
 
 
 Hardening wrapper detected, no tests performed.
 
+=item B<32>
+
+Invalid CMake version used. See B<I-invalid-cmake-used> under L</"BUILDD
+TAGS"> for a detailed explanation.
+
 =back
 
 =head1 AUTHOR
 =back
 
 =head1 AUTHOR