Commit graph

438 commits

Author SHA1 Message Date
Ondrej Mosnacek
bdbe52be1b
libsemanage: always write kernel policy when check_ext_changes is specified
For the use case of rebuilding the policy after package updates, we need
the check_ext_changes operation to always do at least the do_write_kernel
step, because the various semanage dbs may have also changed content
relative to the current binary policy. As this step is itself relatively
fast, we can do it unconditionally.

Fixes: 286a679fad ("libsemanage: optionally rebuild policy when modules are changed externally")
Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
2022-06-30 21:09:00 +02:00
Petr Lautrbach
0a8c177dac
Update VERSIONs to 3.4 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2022-05-18 16:51:03 +02:00
Petr Lautrbach
9df28c241a
Update VERSIONs to 3.4-rc3 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2022-05-04 19:20:37 +02:00
Petr Lautrbach
2a167d1156
Update VERSIONs to 3.4-rc2 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2022-04-20 21:48:57 +02:00
Christian Göttsche
0ba7e23189 libsemanage: ignore missing prototypes in swig generated code
The code generated by swig triggers the following warning:

    semanageswig_wrap.c:2759:24: warning: no previous prototype for ‘PyInit__semanage’ [-Wmissing-prototypes]
     2759 | #  define SWIG_init    PyInit__semanage
          |                        ^~~~~~~~~~~~~~~~
    semanageswig_wrap.c:17772:1: note: in expansion of macro ‘SWIG_init’
    17772 | SWIG_init(void) {
          | ^~~~~~~~~

Ignore -Wmissing-prototypes for swig generated source files.

Acked-by: James Carter <jwcart2@gmail.com>
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2022-04-20 14:03:44 -04:00
James Carter
e1b7b29027 libsemanage/tests: Remove unused functions
The functions helper_port_validate_local_proto(), get_type(), and
get_fcontext_new() are not used, so remove them.

Signed-off-by: James Carter <jwcart2@gmail.com>
2022-04-20 14:03:44 -04:00
James Carter
031c033046 libsemanage/tests: Declare file local functions as static
This is needed to use "-Wmissing-prototypes".

Signed-off-by: James Carter <jwcart2@gmail.com>
2022-04-20 14:03:44 -04:00
Christian Göttsche
fd67b2f4b1 Correct misc typos
Found by typos[1].

[1]: https://github.com/crate-ci/typos

Acked-by: James Carter <jwcart2@gmail.com>
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2022-04-12 13:09:52 -04:00
Christian Göttsche
e205e3e84a libsemanage: avoid double fclose
The cleanup goto block in `semanage_direct_set_enabled()` closes the
file stream pointer fp if not NULL.  Set the stream to NULL after a
manual fclose(3), even on failure.

    direct_api.c: In function ‘semanage_direct_set_enabled’:
    direct_api.c:2130:25: error: pointer ‘fp’ may be used after ‘fclose’ [-Werror=use-after-free]
     2130 |         if (fp != NULL) fclose(fp);
          |                         ^~~~~~~~~~
    direct_api.c:2092:29: note: call to ‘fclose’ here
     2092 |                         if (fclose(fp) != 0) {
          |                             ^~~~~~~~~~

Acked-by: James Carter <jwcart2@gmail.com>
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2022-04-12 13:09:50 -04:00
Petr Lautrbach
73562de8fc
Update VERSIONs to 3.4-rc1 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2022-04-06 19:53:39 +02:00
Petr Lautrbach
28510556f8 libsemanage: Fix USE_AFTER_FREE (CWE-672) in semanage_direct_get_module_info()
>From fclose(3):
Upon successful completion, 0 is returned.  Otherwise, EOF is returned
and errno is set to indicate the error. In either case, any further
access (including another call to fclose()) to the stream results in
undefined behavior.

Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
Acked-by: James Carter <jwcart2@gmail.com>
2022-04-06 10:54:58 +02:00
Petr Lautrbach
c7a3b93e31 libsemanage: Fall back to semanage_copy_dir when rename() fails
In some circumstances, like semanage-store being on overlayfs, rename()
could fail with EXDEV - Invalid cross-device link. This is due to the
fact that overlays doesn't support rename() if source and target are not
on the same layer, e.g. in containers built from several layers. Even
though it's not atomic operation, it's better to try to copy files from
src to dst on our own in this case. Next rebuild will probably not fail
as the new directories will be on the same layer.

Fixes: https://github.com/SELinuxProject/selinux/issues/343

Reproducer:

    $ cd selinux1

    $ cat Dockerfile
    FROM fedora:35
    RUN dnf install -y selinux-policy selinux-policy-targeted

    $ podman build -t localhost/selinux . --no-cache

    $ cd ../selinux2

    $ cat Dockerfile
    FROM localhost/selinux
    RUN semodule -B

    $ podman build -t localhost/selinux2 . --no-cache
    STEP 2/2: RUN semodule -B
    libsemanage.semanage_commit_sandbox: Error while renaming /var/lib/selinux/targeted/active to /var/lib/selinux/targeted/previous. (Invalid cross-device link).
    semodule:  Failed!
    Error: error building at STEP "RUN semodule -B": error while running runtime: exit status 1

With the fix:

    $ podman build -t localhost/selinux2 . --no-cache
    STEP 2/2: RUN semodule -B
    libsemanage.semanage_rename: Warning: rename(/var/lib/selinux/targeted/active, /var/lib/selinux/targeted/previous) failed: Invalid cross-device link, fall back to non-atomic semanage_copy_dir_flags()

    COMMIT localhost/selinux2
    --> d2cfcebc1a1
    Successfully tagged localhost/selinux2:latest
    d2cfcebc1a1b34f1c2cd661ac18292b0612c3e5fa71d6fa1441be244da91b1af

Reported-by: Joseph Marrero Corchado <jmarrero@redhat.com>
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
Acked-by: Ondrej Mosnacek <omosnace@redhat.com>
2022-04-06 10:44:05 +02:00
Vit Mojzis
c79d38ff0c libsemanage: allow spaces in user/group names
"semanage login -a" accepts whitespaces in user/group name
(e.g. users/groups from Active Directory), which may lead to issues down
the line since libsemanage doesn't expect whitespaces in
/var/lib/selinux/targeted/active/seusers and other config files.

Fixes:
  Artificial but simple reproducer
  # groupadd server_admins
  # sed -i "s/^server_admins/server admins/" /etc/group
  # semanage login -a -s staff_u %server\ admins
  # semanage login -l  (or "semodule -B")
  libsemanage.parse_assert_ch: expected character ':', but found 'a' (/var/lib/selinux/targeted/active/seusers: 6):
  %server admins:staff_u:s0-s0:c0.c1023 (No such file or directory).
  libsemanage.seuser_parse: could not parse seuser record (No such file or directory).
  libsemanage.dbase_file_cache: could not cache file database (No such file or directory).
  libsemanage.enter_ro: could not enter read-only section (No such file or directory).
  FileNotFoundError: [Errno 2] No such file or directory

Signed-off-by: Vit Mojzis <vmojzis@redhat.com>
2022-03-03 12:10:03 -05:00
Ondrej Mosnacek
286a679fad libsemanage: optionally rebuild policy when modules are changed externally
In Fedora/RHEL's selinux-policy package we ship a pre-built SELinux
policy store in the RPMs. When updating the main policy RPM, care must
be taken to rebuild the policy using `semodule -B` if there are any
other SELinux modules installed (whether shipped via another RPM or
manually installed locally).

However, this way of shipping/managing the policy creates complications
on systems, where system files are managed by rpm-ostree (such as Fedora
CoreOS or Red Hat CoreOS), where the "package update" process is more
sophisticated.

(Disclaimer: The following is written according to my current limited
understanding of rpm-ostree and may not be entirely accurate, but the
gist of it should match the reality.)

Basically, one can think of rpm-ostree as a kind of Git for system
files. The package content is provided on a "branch", where each
"commit" represents a set of package updates layered on top of the
previous commit (i.e. it is a rolling release with some defined
package content snapshots). The user can then maintain their own branch
with additional package updates/installations/... and "rebase" it on top
of the main branch as needed. On top of that, the user can also have
additional configuration files (or modifications to existing files) in
/etc, which represent an additional layer on top of the package content.

When updating the system (i.e. rebasing on a new "commit" of the "main
branch"), the files on the running system are not touched and the new
system state is prepared under a new root directory, which is chrooted
into on the next reboot.

When an rpm-ostree system is updated, there are three moments when the
SELinux module store needs to be rebuilt to ensure that all modules are
included in the binary policy:
1. When the local RPM installations are applied on top of the base
   system snapshot.
2. When local user configuartion is applied on top of that.
3. On system shutdown, to ensure that any changes in local configuration
   performed since (2.) are reflected in the final new system image.

Forcing a full rebuild at each step is not optimal and in many cases is
not necessary, as the user may not have any custom modules installed.

Thus, this patch extends libsemanage to compute a checksum of the
content of all enabled modules, which is stored in the store, and adds a
flag to the libsemanage handle that instructs it to check the module
content checksum against the one from the last successful transaction
and force a full policy rebuild if they don't match.

This will allow rpm-ostree systems to potentially reduce delays when
reconciling the module store when applying updates.

I wasn't able to measure any noticeable overhead of the hash
computation, which is now added for every transaction (both before and
after this change a full policy rebuild took about 7 seconds on my test
x86 VM). With the new option check_ext_changes enabled, rebuilding a
policy store with unchanged modules took only about 0.96 seconds.

Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
2022-02-18 11:08:39 -05:00
Ondrej Mosnacek
df9f71ab50 libsemanage: clean up semanage_direct_commit() a bit
Do some minor cosmetic cleanup, mainly to eliminate the 'rebuilt' goto
label.

Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
2022-02-18 11:08:36 -05:00
Ondrej Mosnacek
d01ec02fb9 libsemanage: move compressed file handling into a separate object
In order to reduce exisiting and future code duplication and to avoid
some unnecessary allocations and copying, factor the compressed file
utility functions out into a separate C/header file and refactor their
interface.

Note that this change effectively removes the __fsetlocking(3) call from
semanage_load_files() - I haven't been able to figure out what purpose
it serves, but it seems pointless...

Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
2022-02-18 11:08:34 -05:00
Ondrej Mosnacek
67e6201bc8 semodule,libsemanage: move module hashing into libsemanage
The main goal of this move is to have the SHA-256 implementation under
libsemanage, since upcoming patches will make use of SHA-256 for a
different (but similar) purpose in libsemanage. Having the hashing code
in libsemanage will reduce code duplication and allow for easier hash
algorithm upgrade in the future.

Note that libselinux currently also contains a hash function
implementation (for yet another different purpose). This patch doesn't
make any effort to address that duplicity yet.

This patch also changes the format of the hash string printed by
semodule to include the name of the hash. The intent is to avoid
ambiguity and potential collisions when the algorithm is potentially
changed in the future.

Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
2022-02-18 11:08:30 -05:00
Ondrej Mosnacek
6f9e771987 libsemanage: add missing include to boolean_record.c
It uses asprintf(3), but doesn't directly include <stdio.h> - fix it.

Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
2022-02-18 11:08:27 -05:00
Christian Göttsche
f7ec4b4a84 libsemanage: add extern prototype for legacy function
modules.c:171:13: warning: no previous prototype for ‘semanage_module_get_version’ [-Wmissing-prototypes]
      171 | const char *semanage_module_get_version(semanage_module_info_t * modinfo
          |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2021-11-15 16:00:54 -05:00
Christian Göttsche
35273aa2bf libsemanage: include paired header for prototypes
context_record.c:11:13: warning: no previous prototype for ‘semanage_context_get_user’ [-Wmissing-prototypes]
       11 | const char *semanage_context_get_user(const semanage_context_t * con)
          |             ^~~~~~~~~~~~~~~~~~~~~~~~~

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2021-11-15 16:00:54 -05:00
Christian Göttsche
1927c1dfcc libsemanage: mark local functions static
utilities.c:295:18: warning: no previous prototype for ‘list_addafter_controlmem’ [-Wmissing-prototypes]
      295 | semanage_list_t *list_addafter_controlmem(semanage_list_t * item, char *data)
          |                  ^~~~~~~~~~~~~~~~~~~~~~~~

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2021-11-15 16:00:54 -05:00
Markus Linnala
7e30a10ba9 Use IANA-managed domain example.com in examples
See: RFC 2606

foo.com seems to be privately owned.

Signed-off-by: Markus Linnala <Markus.Linnala@knowit.fi>
Acked-by: Petr Lautrbach <plautrba@redhat.com>
2021-11-15 10:53:27 +01:00
Christian Göttsche
fe01a91a79
libsemanage/tests: free memory
Free all memory in test cases, reported by LeakSanitizer.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2021-11-11 22:40:30 +01:00
Christian Göttsche
ea539017fb
libsemanage: do not sort empty records
Do not sort empty records to avoid calling qsort(3) with a NULL pointer.
qsort(3) might be annotated with the function attribute nonnull and
UBSan then complains:

    database_join.c:80:2: runtime error: null pointer passed as argument 1, which is declared to never be null

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2021-11-11 22:40:26 +01:00
Petr Lautrbach
7f600c40bc
Update VERSIONs to 3.3 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2021-10-21 16:31:23 +02:00
Petr Lautrbach
5319c49d8a
Update VERSIONs to 3.3-rc3 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2021-10-06 13:28:15 +02:00
Petr Lautrbach
0b833973bf
Update VERSIONs to 3.3-rc2 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2021-09-22 17:14:25 +02:00
Petr Lautrbach
38cb18e931 Update VERSIONs and Python bindings version to 3.3-rc1 for release
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2021-09-08 09:49:46 +02:00
Petr Lautrbach
d003c4bad4 libsemanage: Fix USE_AFTER_FREE (CWE-672) in semanage_direct_write_langext()
>From fclose(3):
Upon successful completion, 0 is returned.  Otherwise, EOF is returned
and errno is set to indicate the error. In either case, any further
access (including another call to fclose()) to the stream results in
undefined behavior.

Fixes:
    Error: USE_AFTER_FREE (CWE-672): [#def1]
    libsemanage-3.2/src/direct_api.c:1023: freed_arg: "fclose" frees "fp".
    libsemanage-3.2/src/direct_api.c:1034: use_closed_file: Calling "fclose" uses file handle "fp" after closing it.
    # 1032|
    # 1033|   cleanup:
    # 1034|-> 	if (fp != NULL) fclose(fp);
    # 1035|
    # 1036|   	return ret;

Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2021-07-28 14:23:54 -04:00
Nicolas Iooss
e1c6df329c libsemanage: silence -Wextra-semi-stmt warning
On Ubuntu 20.04, when building with clang -Werror -Wextra-semi-stmt
(which is not the default build configuration), the compiler reports:

      genhomedircon.c:742:67: error: empty expression statement has no
      effect; remove unnecessary ';' to silence this warning
      [-Werror,-Wextra-semi-stmt]
              const semanage_seuser_t **u2 = (const semanage_seuser_t **) arg2;;
                                                                               ^

Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
2021-07-06 11:08:11 -04:00
HuaxinLu
6bff61c598 libsemanage: fix use-after-free in parse_module_store()
The passing parameter "arg" of parse_module_store will be freed after
calling. A copy of parameter should be used instead of itself.

Signed-off-by: HuaxinLu <luhuaxin1@foxmail.com>
Acked-by: James Carter <jwcart2@gmail.com>
2021-06-18 16:48:57 +02:00
Petr Lautrbach
cf853c1a0c
Update VERSIONs to 3.2 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2021-03-04 16:42:59 +01:00
Petr Lautrbach
d4d1f4ba7e
Update VERSIONs to 3.2-rc3 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2021-02-24 15:49:59 +01:00
Petr Lautrbach
2c7c4a84c3
Update VERSIONs to 3.2-rc2 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2021-02-03 11:26:28 +01:00
Petr Lautrbach
c35919a703 libsemanage: sync filesystem with sandbox
Commit 331a109f91 ("libsemanage: fsync final files before rename")
added fsync() for policy files and improved situation when something
unexpected happens right after rename(). However the module store could
be affected as well. After the following steps module files could be 0
size:

1. Run `semanage fcontext -a -t var_t "/tmp/abc"`
2. Force shutdown the server during the command is run, or right after
   it's finished
3. Boot the system and look for empty files:
    # find /var/lib/selinux/targeted/ -type f -size 0 | wc -l
    1266

It looks like this situation can be avoided if the filesystem with the
sandbox is sync()ed before we start to rename() directories in the
store.

Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
2021-02-01 15:11:40 +01:00
Petr Lautrbach
c534d4e2ce
Update VERSIONs and Python bindings version to 3.2-rc1 for release
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2021-01-20 12:40:14 +01:00
Petr Lautrbach
5b05e829da
Revert "libsemanage/genhomedircon: check usepasswd"
This reverts commit ce46daab7c.

The behavior described in the reverted commit is correct. `useradd -Z`
creates new mapping between new created user and *unconfined_u*,
`genhomedircon` then uses this new mapping, not /etc/passwd entries, for
generating new homedir contexts.

Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2020-12-27 11:27:57 +01:00
Jakub Hrozek
edae9275f6
libsemanage: Free contents of modkey in semanage_direct_remove
semanage_direct_remove allocates struct semanage_module_key_t on
stack, then calls semanage_module_key_set_name which allocates
modkey->name on heap, but modkey->name wasn't free()-d anywhere,
creating a small leak.

Signed-off-by: Jakub Hrozek <jhrozek@redhat.com>
2020-12-27 11:23:32 +01:00
Vit Mojzis
ce46daab7c libsemanage/genhomedircon: check usepasswd
Only add user homedir contexts when usepasswd = True

Resolves:
   # grep usepasswd /etc/selinux/semanage.conf
   usepasswd=False
   # useradd -Z unconfined_u -d /tmp test
   # matchpathcon /tmp
   /tmp	unconfined_u:object_r:user_home_dir_t:s0

Signed-off-by: Vit Mojzis <vmojzis@redhat.com>
2020-11-10 07:23:44 +01:00
Petr Lautrbach
6ebb35d261
libsemanage: Bump libsemanage.so version
Previous commits removed some symbols and broke ABI, therefore we need to change
SONAME.

See the following quotes from distribution guidelines:

https://www.debian.org/doc/debian-policy/ch-sharedlibs.html#run-time-shared-libraries

Every time the shared library ABI changes in a way that may break
binaries linked against older versions of the shared library, the SONAME
of the library and the corresponding name for the binary package
containing the runtime shared library should change.

https://docs.fedoraproject.org/en-US/packaging-guidelines/#_downstream_so_name_versioning

When new versions of the library are released, you should use an ABI
comparison tool to check for ABI differences in the built shared
libraries. If it detects any incompatibilities, bump the n number by
one.

Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2020-10-19 22:11:42 +02:00
Petr Lautrbach
c08b73d718
libsemanage: Drop deprecated functions
semanage_module_enable() and semanage_module_disable() were deprecated
by commit 9fbc6d1441 ("libsemanage: add back original module
enable/disable functions for ABI compatability") in 2014 in order to
preserve ABI compatibility. As we the libsemanage ABI is changed by the
previous commit, it makes sense to drop them completely.

Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2020-10-19 22:11:41 +02:00
Petr Lautrbach
b46406de8a
libsemanage: Remove legacy and duplicate symbols
Versioned duplicate symbols cause problems for LTO. These symbols were
introduced during the CIL integration several releases ago and were only
consumed by other SELinux userspace components.

Related: https://github.com/SELinuxProject/selinux/issues/245

Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2020-10-19 22:11:40 +02:00
Petr Lautrbach
7df27b78e9 Update VERSIONs and Python bindings version to 3.1 for release
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2020-07-10 17:17:15 +02:00
Petr Lautrbach
b3d8b99f0c Update VERSIONs to 3.1-rc2 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2020-06-19 13:02:31 +02:00
Petr Lautrbach
c554c3d88a Update VERSIONs to 3.1-rc1 for release.
Signed-off-by: Petr Lautrbach <plautrba@redhat.com>
2020-05-15 15:54:08 +02:00
Stephen Smalley
331a109f91 libsemanage: fsync final files before rename
Prior to rename(2)'ing the final selinux policy files into place,
fsync(2) them to ensure the contents will be fully written prior to
rename.  While we are here, also fix checking of write(2) to detect
short writes and treat them as an error.  This code could be more
generally improved but keeping to the minimal changes required to fix
this bug.

Fixes: https://github.com/SELinuxProject/selinux/issues/237
Signed-off-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
2020-05-15 12:40:34 +02:00
Christian Göttsche
43e1a54b02
libsemanage: clarify handle-unknown configuration setting in man page
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2020-04-28 09:27:49 +02:00
Nicolas Iooss
417aff7266 libselinux,libsemanage: remove double blank lines
This looks cleaner.

Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
2020-04-22 16:40:34 -05:00
Christian Göttsche
21f50e94b9
tree-wide: use python module importlib instead of the deprecated imp
Replace

python3 -c 'import imp;print([s for s,m,t in imp.get_suffixes() if t == imp.C_EXTENSION][0])'
<string>:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
.cpython-38-x86_64-linux-gnu.so

with

python3 -c 'import importlib.machinery;print(importlib.machinery.EXTENSION_SUFFIXES[0])'
.cpython-38-x86_64-linux-gnu.so

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2020-04-16 18:50:43 +02:00
William Roberts
5eee91221b libsemanage: rm semanage_module_upgrade_info from map
This routine was never defined, just declared as a prototype.
Thus it never really existed, but remained in the map file.
Remove it.

Acked-by: Nicolas Iooss <nicolas.iooss@m4x.org>
Signed-off-by: William Roberts <william.c.roberts@intel.com>
2020-04-15 10:28:58 -05:00