Skip to content

Commit

Permalink
[uCode] Add experimental check for pending AMD microcode updates (git…
Browse files Browse the repository at this point in the history
…hub issue #150).
  • Loading branch information
liske committed Dec 23, 2019
1 parent 4066679 commit f3dd146
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 58 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
needrestart (3.5) unstable; urgency=high

* Features:
- [uCode] Check for pending AMD microcode updates (experimental).
(Debian Bug#886611 by Paul Wise <pabs@debian.org>)
(github issue #150 by Tom Reynolds @tomreyn and Mark Wagie @yochananmarqos)

* Changes:
[Core] Add network.service to blacklist.
(github pull request #145 by Marc Dequènes (Duck) @duck-rh)
Expand Down
101 changes: 77 additions & 24 deletions perl/lib/NeedRestart/uCode.pm
Original file line number Diff line number Diff line change
Expand Up @@ -40,51 +40,104 @@ require Exporter;
our @ISA = qw(Exporter);

our @EXPORT = qw(
nr_ucode_check
NRM_UNKNOWN
NRM_CURRENT
NRM_OBSOLETE
nr_ucode_check
nr_ucode_register
NRM_UNKNOWN
NRM_CURRENT
NRM_OBSOLETE
);

my $LOGPREF = '[ucode]';
my @PKGS;

sub nr_ucode_check {
my $debug = shift;
my $ui = shift;
my $ui = shift;
my @PKGS;

# autoload ucode modules
foreach my $module (findsubmod NeedRestart::uCode) {
unless(eval "use $module; ${module}::nr_ucode_init(\$debug);") {
warn "Failed to load $module: $@" if($@ && $debug);
foreach my $module ( findsubmod NeedRestart::uCode ) {
unless ( eval "use $module (); 1;" ) {
warn "$LOGPREF Failed to load $module: $@" if ( $@ && $debug );
}
else {
push(@PKGS, $module);
}
print STDERR "$LOGPREF using $module\n"
if ($debug);

push( @PKGS, $module );
}
}

unless(scalar @PKGS > 0) {
print STDERR "$LOGPREF no supported processor microcode detection\n" if($debug);
return (NRM_UNKNOWN, ());
unless ( scalar @PKGS > 0 ) {
print STDERR "$LOGPREF no supported processor microcode detection\n"
if ($debug);
return ( NRM_UNKNOWN, () );
}

$ui->progress_prep(scalar @PKGS, __ 'Scanning processor microcode...');
# parse /proc/cpuinfo
my %processors;
my %sockels;
{
my $fh;
unless ( open( $fh, '<', '/proc/cpuinfo' ) ) {
warn "$LOGPREF Failed to read /proc/cpuinfo: $!\n"
if ($debug);
return ( NRM_UNKNOWN, () );
}

# autoload ucode modules
my ($state, @vars) = (NRM_UNKNOWN);
foreach my $pkg (@PKGS) {
eval "(\$state, \@vars) = ${pkg}::nr_ucode_check_real(\$debug, \$ui);";
local $/ = "\n\n";

$ui->progress_step;
while (<$fh>) {

if($state == NRM_OBSOLETE) {
$ui->progress_fin;
return ($state, @vars)
# transform key: value into hash
my %data;
foreach ( split(/\n+/) ) {
$data{$1} = $2 if (/^(.+\S)\s*: (.+)$/);
}

if ( defined( $data{processor} ) ) {

# save processor details
$processors{ $data{processor} } = \%data;

# save physical to logical mapping
my $sockel = 0;
if ( defined( $data{'physical id'} ) ) {
$sockel = $data{'physical id'};
}
push( @{ $sockels{$sockel} }, $data{processor} );
}
}
}

$ui->progress_prep( (scalar keys %sockels) * (scalar @PKGS),
__ 'Scanning processor microcode...' );

my ( $state, @vars ) = (NRM_UNKNOWN);
foreach my $sid ( keys %sockels ) {
my $pid = $sockels{$sid}[0];

# call ucode modules
foreach my $pkg (@PKGS) {
my ( $nstate, @nvars ) = (NRM_UNKNOWN);
eval
"(\$nstate, \@nvars) = ${pkg}::nr_ucode_check_real(\$debug, \$ui, \$processors{\$pid});";
print STDERR $@
if ( $@ && $debug );
$ui->progress_step;

if ( $nstate > $state ) {
( $state, @vars ) = ( $nstate, @nvars );
}

if ( $nstate == NRM_OBSOLETE ) {
last;
}
}
}

$ui->progress_fin;
return ($state, @vars);

return ( $state, @vars );
}

1;
202 changes: 202 additions & 0 deletions perl/lib/NeedRestart/uCode/AMD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# needrestart - Restart daemons after library updates.
#
# Authors:
# Thomas Liske <thomas@fiasko-nw.net>
#
# Copyright Holder:
# 2013 - 2019 (C) Thomas Liske [http://fiasko-nw.net/~thomas/]
#
# License:
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this package; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#

#
# This package is based on the result of the following paper:
#
# Security Analysis of x86 Processor Microcode
# Daming D. Chen <ddchen@asu.edu>
# Gail-Joon Ahn <gahn@asu.edu>
#
# https://www.dcddcc.com/docs/2014_paper_microcode.pdf
#
package NeedRestart::uCode::AMD;

use strict;
use warnings;
use NeedRestart::uCode;
use NeedRestart::Utils;
use File::Basename;
use POSIX qw(uname);
use Locale::TextDomain 'needrestart';

my $LOGPREF = '[uCode/AMD]';

sub nr_ucode_init {
my ( $sysname, $nodename, $release, $version, $machine ) = uname;
my $is_x86 = ( $machine =~ /^(i\d86|x86_64)$/ );

die "$LOGPREF Not running on x86!\n" unless ($is_x86);
}

my $_ucodes;

sub _scan_ucodes {
my $debug = shift;

# scan AMD ucode files
foreach my $fn (</lib/firmware/amd-ucode/microcode_*.bin>) {
my $bn = basename( $fn, '.bin' );
my $fh;

unless ( open( $fh, '<:raw', $fn ) ) {
warn "$LOGPREF Failed to open ucode source file '$fn': $!\n";
next;
}
my @stat = stat($fh);

my $fpos = read( $fh, my $buf, 12 );
my ( $hdr_magic, $hdr_type, $hdr_size ) = unpack( 'a4VV', $buf );

if ( $hdr_magic ne "DMA\0" ) {
warn "$LOGPREF Invalid magic header ($hdr_magic)!\n";
next;
}
if ( $hdr_type != 0 ) {
warn "$LOGPREF Unsupported table type $hdr_type!\n";
next;
}

for ( ; $fpos < $hdr_size ; ) {
$fpos += read( $fh, $buf, 16 );
my ( $pkg_cpuid, $pkg_errmask, $pkg_errcomp, $pkg_prid, $pkg_unk )
= unpack( 'VVVvv', $buf );

if ( $pkg_cpuid > 0 ) {
$_ucodes->{cpuid}->{$pkg_cpuid} = $pkg_prid;
}
}

for ( ; $fpos < $stat[7] ; ) {
$fpos += read( $fh, $buf, 8 );
my ( $upd_type, $upd_size ) = unpack( 'VV', $buf );

$fpos += read( $fh, $buf, $upd_size );
my (
$pat_date, $pat_pid, $pat_did, $pat_dlen, $pat_iflg,
$pat_dchk, $pat_ndid, $pat_sdid, $pat_prid
) = unpack( 'VVvCCVVVv', $buf );

$_ucodes->{prid}->{$pat_prid} = $pat_pid;
}
}
}

sub nr_ucode_check_real {
my $debug = shift;
my $ui = shift;
my $info = shift;

# check for AMD cpu
unless ( defined( $info->{vendor_id} )
&& $info->{vendor_id} eq 'AuthenticAMD' )
{
die "$LOGPREF #$info->{processor} cpu vendor id mismatch\n";
}

# get CPUID using kernel module
my $cpuid;
if ( open( my $fh, '<:raw', "/dev/cpu/$info->{processor}/cpuid" ) ) {
seek( $fh, 1, 0 );
read( $fh, my $eax, 16 );
close($fh);
$cpuid = unpack( 'V', $eax );
printf( STDERR
"$LOGPREF #$info->{processor} cpuid 0x%08x (/dev/cpu/$info->{processor}/cpuid)\n",
$cpuid
) if ($debug);
}
else {
warn
"$LOGPREF #$info->{processor} Failed to open /dev/cpu/$info->{processor}/cpuid (Missed \`modprobe cpuid\`?): $!\n"
if ($debug);
}

# get CPUID from /proc/cpuinfo
my $family = int( $info->{'cpu family'} );
my $xfamily = 0;
if ( $family > 0xf ) {
$xfamily = $family - 0xf;
$family = 0xf;
}

my $model = int( $info->{model} );
my $xmodel = $model >> 4;
$model = $model & 0xf;

my $stepping = int( $info->{stepping} );
my $eax =
( ( ( $xfamily & 0xff ) << 20 ) +
( ( $xmodel & 0xf ) << 16 ) +
( ( $family & 0xf ) << 8 ) +
( ( $model & 0xf ) << 4 ) +
( ( $stepping & 0xf ) << 0 ) );

printf( STDERR "$LOGPREF #$info->{processor} cpuid 0x%08x (/proc/cpuinfo)\n", $eax )
if ($debug);

if ($cpuid) {
if ( $cpuid != $eax ) {
warn "$LOGPREF #$info->{processor} CPUID mismatch detected!\n" if ($debug);
}
}
else {
$cpuid = $eax;
}

# get microcode version of cpu
my $ucode = hex( $info->{microcode} );
printf( STDERR "$LOGPREF #$info->{processor} running ucode 0x%08x\n", $ucode ) if ($debug);

unless ( defined($_ucodes) ) {
_scan_ucodes();
}

my %vars = ( CURRENT => sprintf( "0x%08x", $ucode ), );

# check for microcode updates
if ( exists( $_ucodes->{cpuid}->{$cpuid} ) ) {
my $prid = $_ucodes->{cpuid}->{$cpuid};
if ( exists( $_ucodes->{prid}->{$prid} ) ) {
$vars{AVAIL} = sprintf( "0x%08x", $_ucodes->{prid}->{$prid} ),

print STDERR "$LOGPREF #$info->{processor} found ucode $vars{AVAIL}\n" if ($debug);
if ( $_ucodes->{prid}->{$prid} > $ucode ) {
return ( NRM_OBSOLETE, %vars );
}
}
else {
print STDERR "$LOGPREF #$info->{processor} no ucode updates available\n" if ($debug);
}
return ( NRM_CURRENT, %vars );
}
else {
print STDERR "$LOGPREF #$info->{processor} no ucode updates available\n" if ($debug);
return ( NRM_CURRENT, %vars );
}

return ( NRM_UNKNOWN, %vars );
}

1;
Loading

0 comments on commit f3dd146

Please sign in to comment.