├── requirements.txt ├── src └── elivepatch_client │ ├── __init__.py │ ├── version.py │ ├── __main__.py │ ├── argsparser.py │ ├── checkers.py │ ├── security.py │ ├── cli.py │ ├── log.py │ ├── patch.py │ └── restful.py ├── .travis.yml ├── example ├── 2.patch └── 1.patch ├── setup.py ├── .gitignore ├── README.md └── LICENSE /requirements.txt: -------------------------------------------------------------------------------- 1 | GitPython 2 | requests 3 | -------------------------------------------------------------------------------- /src/elivepatch_client/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # (c) 2017, Alice Ferrazzi 5 | # Distributed under the terms of the GNU General Public License v2 or later 6 | 7 | __version__ = '0.1' 8 | __author__ = 'Alice Ferrazzi' 9 | __license__ = 'GNU GPLv2+' -------------------------------------------------------------------------------- /src/elivepatch_client/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # (c) 2017, Alice Ferrazzi 5 | # Distributed under the terms of the GNU General Public License v2 or later 6 | 7 | 8 | from __future__ import unicode_literals 9 | 10 | VERSION = '0.1' 11 | 12 | if __name__ == '__main__': 13 | print(VERSION) -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | services: 2 | - docker 3 | before_install: 4 | - docker pull alice2f/elivepatch-server:latest 5 | - docker run -d --name elivepatch-server -p 5000:5000 alice2f/elivepatch-server 6 | - docker ps -a 7 | language: python 8 | sudo: required 9 | python: 10 | - '3.6' 11 | cache: pip 12 | script: 13 | - python setup.py install 14 | - elivepatch-client -p example/2.patch -k example/config_5.1.6 -a 5.1.6 --url http://localhost:5000 & 15 | - docker logs elivepatch-server 16 | -------------------------------------------------------------------------------- /example/2.patch: -------------------------------------------------------------------------------- 1 | --- src.org/fs/proc/meminfo.c.orig 2018-10-31 12:00:49.100000000 +0000 2 | +++ src/fs/proc/meminfo.c 2018-10-31 12:01:42.370000000 +0000 3 | @@ -121,7 +121,7 @@ static int meminfo_proc_show(struct seq_ 4 | seq_printf(m, "VmallocTotal: %8lu kB\n", 5 | (unsigned long)VMALLOC_TOTAL >> 10); 6 | show_val_kb(m, "VmallocUsed: ", 0ul); 7 | - show_val_kb(m, "VmallocChunk: ", 0ul); 8 | + show_val_kb(m, "VMALLOCCHUNK: ", 0ul); 9 | show_val_kb(m, "Percpu: ", pcpu_nr_pages()); 10 | 11 | #ifdef CONFIG_MEMORY_FAILURE 12 | -------------------------------------------------------------------------------- /example/1.patch: -------------------------------------------------------------------------------- 1 | From 9e9a69f979c701a7e1be91a8508d18868e7bab28 Mon Sep 17 00:00:00 2001 2 | From: "Jason A. Donenfeld" 3 | Date: Thu, 8 Jun 2017 00:36:14 +0200 4 | Subject: [PATCH] exec: restrict argv size for suid binaries 5 | 6 | Signed-off-by: Jason A. Donenfeld 7 | --- 8 | fs/exec.c | 7 +++++++ 9 | 1 file changed, 7 insertions(+) 10 | 11 | diff --git a/fs/exec.c b/fs/exec.c 12 | index 72934df68471..631f5374201f 100644 13 | --- a/fs/exec.c 14 | +++ b/fs/exec.c 15 | @@ -243,6 +243,13 @@ static struct page *get_arg_page(struct linux_binprm *bprm, unsigned long pos, 16 | put_page(page); 17 | return NULL; 18 | } 19 | + 20 | + if (size > (512UL << 10) && 21 | + (!uid_eq(bprm->cred->euid, current_euid()) || 22 | + !gid_eq(bprm->cred->egid, current_egid()))) { 23 | + put_page(page); 24 | + return NULL; 25 | + } 26 | } 27 | 28 | return page; 29 | -- 30 | 2.13.0 31 | 32 | -------------------------------------------------------------------------------- /src/elivepatch_client/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # (c) 2017, Alice Ferrazzi 5 | # Distributed under the terms of the GNU General Public License v2 or later 6 | 7 | __version__ = '0.1' 8 | __author__ = 'Alice Ferrazzi' 9 | __license__ = 'GNU GPLv2+' 10 | 11 | #=============================================================================== 12 | # 13 | # MAIN 14 | # 15 | #------------------------------------------------------------------------------- 16 | def main(): 17 | from .argsparser import ArgsParser 18 | from .cli import Main 19 | #import pdb; pdb.set_trace() 20 | import os 21 | 22 | root = None 23 | try: 24 | root = os.environ['ROOT'] 25 | except KeyError: 26 | pass 27 | 28 | main = Main(ArgsParser()) 29 | try: 30 | main() 31 | except KeyboardInterrupt: 32 | print('Interrupt received, exiting...') 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='elivepatch-client', 5 | version='0.1', 6 | description='Distributed live patch client and automatic kernel live patch for kernel CVE', 7 | url='https://wiki.gentoo.org/wiki/Elivepatch, ' +\ 8 | 'https://github.com/aliceinwire/elivepatch-client', 9 | 10 | classifiers=[ 11 | 'Development Status :: 4 - Beta', 12 | 'Environment :: Console', 13 | 'Intended Audience :: Developers', 14 | 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 15 | 'Operating System :: OS Independent', 16 | 'Programming Language :: Python', 17 | 'Programming Language :: Python :: 2.6', 18 | 'Programming Language :: Python :: 2.7', 19 | 'Programming Language :: Python :: 3', 20 | 'Programming Language :: Python :: 3.3', 21 | 'Programming Language :: Python :: 3.4', 22 | 'Programming Language :: Python :: 3.5', 23 | 'Topic :: System :: Operating System Kernels', 24 | ], 25 | 26 | author='Alice Ferrazzi', 27 | author_email='alice.ferrazzi@gmail.com', 28 | license='GNU GPLv2+', 29 | packages=find_packages("src"), 30 | package_dir={"": "src"}, 31 | install_requires=["GitPython", "requests"], 32 | entry_points={"console_scripts": ["elivepatch-client = elivepatch_client.__main__:main"]}, 33 | ) 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | 118 | # mypy 119 | .mypy_cache/ 120 | .dmypy.json 121 | dmypy.json 122 | 123 | # Pyre type checker 124 | .pyre/ 125 | -------------------------------------------------------------------------------- /src/elivepatch_client/argsparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # (c) 2017, Alice Ferrazzi 5 | # Distributed under the terms of the GNU General Public License v2 or later 6 | 7 | 8 | import argparse 9 | try: 10 | import ConfigParser 11 | except: 12 | import configparser as ConfigParser 13 | 14 | class ArgsParser(object): 15 | 16 | def __init__(self): 17 | conf_parser = argparse.ArgumentParser( 18 | # Turn off help, so we print all options in response to -h 19 | add_help=False 20 | ) 21 | conf_parser.add_argument("-c", "--conf_file", 22 | help="Specify config file", metavar="FILE") 23 | args, remaining_argv = conf_parser.parse_known_args() 24 | defaults = { 25 | "config" : "/proc/config.gz", 26 | } 27 | if args.conf_file: 28 | config = ConfigParser.ConfigParser() 29 | config.read([args.conf_file]) 30 | defaults = dict(config.items("Defaults")) 31 | 32 | # Don't surpress add_help here so it will handle -h 33 | parser = argparse.ArgumentParser( 34 | # Inherit options from config_parser 35 | parents=[conf_parser], 36 | # print script description with -h/--help 37 | description=__doc__, 38 | # Don't mess with format of description 39 | formatter_class=argparse.RawDescriptionHelpFormatter, 40 | ) 41 | parser.set_defaults(**defaults) 42 | parser.add_argument("-e","--cve", action='store_true', help="Check for secutiry problems in the kernel.") 43 | parser.add_argument("-p","--patch", help="patch to convert.") 44 | parser.add_argument("-k","--config", help="set kernel config file manually.") 45 | parser.add_argument("-a","--kernel_version", help="set kernel version manually.") 46 | parser.add_argument("-l","--clear", action='store_true', help="Clear the already installed cve db (Use with caution!).") 47 | parser.add_argument("-u","--url", help="set elivepatch server url.") 48 | parser.add_argument("-d","--debug", help="set the debug option.") 49 | parser.add_argument("-o","--log_output", help="set the debug option.") 50 | parser.add_argument("-v","--version", action='store_true', help="show the version.") 51 | self.args = parser.parse_args(remaining_argv) 52 | 53 | def get_arg(self): 54 | return self.args 55 | 56 | -------------------------------------------------------------------------------- /src/elivepatch_client/checkers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # (c) 2017, Alice Ferrazzi 5 | # Distributed under the terms of the GNU General Public License v2 or later 6 | 7 | import gzip 8 | import uuid 9 | import tempfile 10 | 11 | import os 12 | import os.path 13 | import re 14 | 15 | from elivepatch_client import restful 16 | from elivepatch_client import log 17 | 18 | 19 | def id_generate_uuid(): 20 | generated_uuid = str(uuid.uuid4()) 21 | return generated_uuid 22 | 23 | 24 | class Kernel(object): 25 | """ 26 | Manage kernels files 27 | """ 28 | def __init__(self, restserver_url, kernel_version, session_uuid=None): 29 | self.config_fullpath = '' 30 | self.main_patch_fullpath = '' 31 | self.restserver_url = restserver_url 32 | self.kernel_version = kernel_version 33 | if session_uuid: 34 | self.session_uuid = session_uuid 35 | else: 36 | self.session_uuid = id_generate_uuid() 37 | log.notice('This session uuid: ' + str(self.session_uuid)) 38 | self.rest_manager = restful.ManaGer(self.restserver_url, self.kernel_version, self.session_uuid) 39 | 40 | def set_config(self, config_fullpath): 41 | self.config_fullpath = config_fullpath 42 | 43 | def set_main_patch(self, main_patch_fullpath): 44 | self.main_patch_fullpath = main_patch_fullpath 45 | 46 | def send_files(self, incremental_patches_list): 47 | """ 48 | Send config and patch files 49 | 50 | :return: void 51 | """ 52 | f_action = FileAction(self.config_fullpath) 53 | temporary_config = tempfile.NamedTemporaryFile(delete=False) 54 | # check the configuration file 55 | # TODO: make it more compact 56 | if re.findall("[.]gz\Z", self.config_fullpath): 57 | log.notice('gz extension') 58 | # uncompress the gzip config file 59 | # return configuration temporary folder 60 | temporary_config = f_action.decompress_gz(temporary_config) 61 | else: 62 | # read already uncompressed configuration 63 | with open(self.config_fullpath, 'rb') as in_file: 64 | config = in_file.read() 65 | # Store uncompressed temporary file 66 | temporary_config.write(config) 67 | # Get kernel version from the configuration file header 68 | # self.kernel_version = f_action.config_kernel_version(temporary_config) 69 | self.rest_manager.set_kernel_version(self.kernel_version) 70 | log.notice('debug: kernel version = ' + self.rest_manager.get_kernel_version()) 71 | 72 | send_api = '/elivepatch/api/v1.0/get_files' 73 | 74 | # send uncompressed config and patch files fullpath 75 | self.rest_manager.send_files(temporary_config, self.main_patch_fullpath, incremental_patches_list, send_api) 76 | 77 | def get_livepatch(self): 78 | self.rest_manager.get_livepatch(self.main_patch_fullpath) 79 | 80 | 81 | class FileAction(object): 82 | """ 83 | Work with files 84 | """ 85 | def __init__(self, full_path): 86 | self.full_path = full_path 87 | pass 88 | 89 | def decompress_gz(self, temporary): 90 | """ 91 | Uncompress gzipped configuration 92 | :return: Uncompressed configuration file path 93 | """ 94 | path_gz_file = self.full_path 95 | log.notice('path_gz_file: '+ path_gz_file + ' temporary_path_uncompressed_file: ' + 96 | temporary.name) 97 | if os.path.isfile(path_gz_file): 98 | with gzip.open(path_gz_file, 'rb') as in_file: 99 | uncompressed_output = in_file.read() 100 | # Store uncompressed file 101 | temporary.write(uncompressed_output) 102 | return temporary 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elivepatch-client 2 | [![Maintainability](https://api.codeclimate.com/v1/badges/67c1cd220ba85837d96f/maintainability)](https://codeclimate.com/github/gentoo/elivepatch-client/maintainability) 3 | [![Build Status](https://travis-ci.org/gentoo/elivepatch-client.svg?branch=master)](https://travis-ci.org/gentoo/elivepatch-client) 4 | 5 | Flexible Distributed Linux Kernel Live Patching 6 | 7 | 8 | ## Features 9 | 10 | * 3rd-party trust. 11 | * Trust on a third-party service can be eliminated by deploying Elivepatch in-house. 12 | * Custom kernel configurations. 13 | * Live patches can be created for different kernel versions and configurations by varying the parameters to Elivepatch. 14 | * Modified kernels. 15 | * Support is extended to locally modified kernels (e.g. out-of-tree patch sets) by sending the server a list of patches that should be applied before the live patch creation process starts. 16 | * Client-generated patches. 17 | * In Elivepatch, clients specify the live patches to be created whereas current systems only support vendor-generated patches. 18 | * Security auditing. 19 | * Elivepatch is completely open source and thus fully auditable. 20 | 21 | ## User's guide 22 | 23 | ### Installing from source 24 | 25 | ``` 26 | $ git clone https://github.com/gentoo/elivepatch-client 27 | $ cd elivepatch-client/ 28 | $ virtualenv .venv 29 | $ python setup.py install 30 | ``` 31 | 32 | ### Example usage 33 | 34 | ``` 35 | elivepatch-client -p example/2.patch -k example/config_5.1.6 -a 5.1.6 --url http://localhost:5000 36 | ``` 37 | 38 | ### Creating Live patch 39 | Not all patch can be converted to live patch using kpatch. 40 | * [Patch that change data structure](https://github.com/dynup/kpatch/blob/master/doc/patch-author-guide.md#change-the-code-which-uses-the-data-structure) 41 | * [Change content of existing variable](https://github.com/dynup/kpatch/blob/master/doc/patch-author-guide.md#use-a-kpatch-load-hook) 42 | * [Add field to existing data structure](https://github.com/dynup/kpatch/blob/master/doc/patch-author-guide.md#use-a-shadow-variable) 43 | * Init code changes are incompatible with kpatch 44 | * [Header file changes](https://github.com/dynup/kpatch/blob/master/doc/patch-author-guide.md#header-file-changes) 45 | * [Dealing with unexpected changed functions](https://github.com/dynup/kpatch/blob/master/doc/patch-author-guide.md#dealing-with-unexpected-changed-functions) 46 | * [Removing references to static local variables](https://github.com/dynup/kpatch/blob/master/doc/patch-author-guide.md#removing-references-to-static-local-variables) 47 | * [Code removal](https://github.com/dynup/kpatch/blob/master/doc/patch-author-guide.md#code-removal) 48 | 49 | ## Repository 50 | 51 | * [elivepatch-client](https://github.com/gentoo/elivepatch-client) 52 | * Client to be run on the machine where we want to install the live patch. 53 | * [elivepatch-server](https://github.com/gentoo/elivepatch-server) 54 | * RESTful API to be run on the server using kpatch for building the live patch. 55 | * [elivepatch-overlay](https://github.com/elivepatch/livepatch-overlay) 56 | * Where to keep your livepatch patches. 57 | * [elivepatch-docker](https://github.com/elivepatch/elivepatch-docker) 58 | * Simplyfing elivepatch-server start. 59 | 60 | ## Developer's guide 61 | 62 | ### Contributing 63 | 64 | Fork this repo and make a pull request. We are happy to merge it. 65 | 66 | Commit message should look like 67 | 68 | ``` 69 | [category/packagename] short decription 70 | 71 | Long description 72 | ``` 73 | 74 | This makes reading history easier. GPG signing your changes is a good idea. 75 | 76 | If you have push access to this repo it is a good idea to still create a pull request, 77 | so at least one more person have reviewed your code. 78 | Exceptions are trivial changes and urgent changes (that fix something completely broken). 79 | 80 | ### Communication 81 | 82 | - Join #gentoo-kernel channel on Freenode 83 | - Open issues [here](https://github.com/gentoo/elivepatch-client/issues) 84 | -------------------------------------------------------------------------------- /src/elivepatch_client/security.py: -------------------------------------------------------------------------------- 1 | import git 2 | import os 3 | import urllib.request as request 4 | from elivepatch_client import log 5 | import shutil 6 | 7 | 8 | class CVE(object): 9 | """ 10 | Check the kernel against a CVE repository 11 | """ 12 | def __init__(self): 13 | self.git_url = "https://github.com/nluedtke/linux_kernel_cves" 14 | self.repo_dir = "/tmp/kernel_cve/" 15 | self.cve_patches_dir = "/tmp/patches_cve/" 16 | pass 17 | 18 | def git_download(self): 19 | git.Repo.clone_from(self.git_url, self.repo_dir) 20 | 21 | def git_update(self): 22 | cve_repository = git.cmd.Git(self.repo_dir) 23 | cve_repository.pull() 24 | 25 | def set_repo(self, git_url, repo_dir): 26 | self.git_url = git_url 27 | self.repo_dir = repo_dir 28 | 29 | def cve_git_id(self, kernel_version): 30 | major_version, minor_version, revision_version = kernel_version.split('.') 31 | print(major_version, minor_version, revision_version) 32 | security_file = open(self.repo_dir+str(major_version)+"."+str(minor_version)+ 33 | "/"+str(major_version)+"."+str(minor_version)+"_security.txt", "r") 34 | security_versions = [] 35 | for line in security_file: 36 | if "CVEs fixed in" in line: 37 | security_versions_tmp = line.strip().split(' ')[3][:-1] 38 | # if there is not revision, set revision as 0 39 | sv_split_tmp = security_versions_tmp.split('.') 40 | if len(sv_split_tmp) == 2: 41 | security_versions.append(0) 42 | else: 43 | security_versions.append(security_versions_tmp.split('.')[2]) 44 | security_file.close() 45 | 46 | log.notice('[debug] security versions: ' + str(security_versions)) 47 | 48 | cve_2d_list = [] 49 | for version in security_versions: 50 | if int(version) > int(revision_version): 51 | cve_2d_list.append(self.cve_id(major_version, minor_version, version)) 52 | 53 | cve_outfile_list = [] 54 | patch_index = 0 55 | if not os.path.exists(self.cve_patches_dir): 56 | os.mkdir(self.cve_patches_dir) 57 | for cve_list in cve_2d_list: 58 | # Remove duplicated cve_id from the cve list for not add the same patch 59 | cve_list = [ii for n,ii in enumerate(cve_list) if ii not in cve_list[:n]] 60 | for cve_id in cve_list: 61 | cve_outfile = self.download_cve_patch(cve_id, str(patch_index).zfill(5)) 62 | cve_outfile_list.append([cve_outfile[0], cve_outfile[1].name]) 63 | patch_index +=1 64 | return cve_outfile_list 65 | 66 | def download_cve_patch(self, cve_id, patch_index): 67 | file_name= self.cve_patches_dir + patch_index + '.patch' 68 | 69 | # Download the file from `url` and save it locally under `file_name`: 70 | with request.urlopen('https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/patch/?id=' + cve_id[1]) as response, \ 71 | open(file_name, 'wb') as out_file: 72 | shutil.copyfileobj(response, out_file) 73 | return [cve_id[0],out_file] 74 | 75 | def cve_id(self, major_version, minor_version, revision_version): 76 | security_file = open(self.repo_dir+str(major_version)+"."+str(minor_version)+ 77 | "/"+str(major_version)+"."+str(minor_version)+"_security.txt", "r") 78 | 79 | git_security_id = [] 80 | # return cve for a kernel version 81 | for excluded_line in security_file: 82 | if ("CVEs fixed in "+str(major_version)+ 83 | "."+str(minor_version)+ 84 | "."+str(revision_version)+ 85 | ":") in excluded_line: 86 | for included_line in security_file: 87 | if not "\n" is included_line: 88 | git_security_id.append([included_line.strip().split(' ')[0].replace(':',''),included_line.strip().split(' ')[1]]) 89 | else: 90 | break 91 | security_file.close() 92 | return git_security_id 93 | 94 | -------------------------------------------------------------------------------- /src/elivepatch_client/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # (c) 2017, Alice Ferrazzi 5 | # Distributed under the terms of the GNU General Public License v2 or later 6 | 7 | import sys 8 | import os 9 | import shelve 10 | 11 | from elivepatch_client.checkers import Kernel 12 | from elivepatch_client import restful 13 | from elivepatch_client.version import VERSION 14 | from elivepatch_client import patch 15 | from elivepatch_client import security 16 | from elivepatch_client import log 17 | import tempfile 18 | 19 | if sys.hexversion >= 0x30200f0: 20 | ALL_KEYWORD = b'ALL' 21 | else: 22 | ALL_KEYWORD = 'ALL' 23 | 24 | 25 | class Main(object): 26 | """ 27 | Performs the actions selected by the user 28 | """ 29 | 30 | def __init__(self, argparser): 31 | config = argparser.get_arg() 32 | # Initialize the logger before anything else. 33 | config.color = True 34 | log.setup_logging(config.debug, output=config.log_output, debug=config.debug, 35 | color=config.color) 36 | self.dispatch(config) 37 | 38 | def dispatch(self, config): 39 | log.debug(str(config)) 40 | if config.cve: 41 | patch_manager = patch.ManaGer() 42 | applied_patches_list = patch_manager.list(config.kernel_version) 43 | log.notice(applied_patches_list) 44 | cve_repository = security.CVE() 45 | if not os.path.isdir("/tmp/kernel_cve"): 46 | log.notice("Downloading the CVE repository...") 47 | cve_repository.git_download() 48 | else: 49 | log.notice("CVE repository already present.") 50 | log.notice("updating...") 51 | cve_repository.git_update() 52 | if config.clear: 53 | if os.path.isfile('cve_ids'): 54 | os.remove('cve_ids') 55 | cve_patch_list = cve_repository.cve_git_id(config.kernel_version) 56 | new_cve_patch_list = cve_patch_list 57 | cve_previous_patch_list = [] 58 | # checking if we have a previous cve_ids list 59 | if os.path.isfile('cve_ids'): 60 | cve_db = shelve.open('cve_ids') 61 | for i in (list(cve_db.keys())): 62 | cve_previous_patch_list.append([i, cve_db[i]]) 63 | cve_db.close() 64 | new_cve_patch_list = [] 65 | # checking if there is any new cve patch in the repository 66 | for cve_patch_id in cve_patch_list: 67 | if cve_patch_id not in cve_previous_patch_list: 68 | new_cve_patch_list.append(cve_patch_id) 69 | # converting new cve to live patch 70 | for cve_id, cve_patch in new_cve_patch_list: 71 | with shelve.open('cve_ids') as cve_db: 72 | cve_db[cve_id] = cve_patch 73 | 74 | log.notice('merging cve patches...') 75 | with tempfile.NamedTemporaryFile(dir='/tmp/', delete=False) as portage_tmpdir: 76 | log.notice('portage_tmpdir: '+portage_tmpdir.name) 77 | for cve_id, cve_file in cve_patch_list: 78 | with open(cve_file,'rb+') as infile: 79 | portage_tmpdir.write(infile.read()) 80 | livepatch(config.url, config.kernel_version, config.config, portage_tmpdir.name, applied_patches_list) 81 | 82 | log.notice(new_cve_patch_list) 83 | elif config.patch: 84 | patch_manager = patch.ManaGer() 85 | applied_patches_list = patch_manager.list(config.kernel_version) 86 | log.notice(str(applied_patches_list)) 87 | livepatch(config.url, config.kernel_version, config.config, config.patch, applied_patches_list) 88 | 89 | elif config.version: 90 | log.notice('elivepatch version: '+str(VERSION)) 91 | else: 92 | print('--help for help\n\ 93 | you need at list --patch or --cve') 94 | 95 | def __call__(self): 96 | pass 97 | 98 | 99 | def livepatch(url, kernel_version, config, main_patch, incremental_patch_names_list): 100 | """ 101 | Create, get and install the live patch 102 | 103 | :param url: url of the elivepatch_server 104 | :param kernel_version: kernel version of the system to be live patched 105 | :param config: configuration file of the kernel we are going to live patch (DEBUG_INFO is not needed here) 106 | :param main_patch: the main patch that will be converted into a live patch kernel module 107 | :param incremental_patch_names_list: list of patch path that are already used in the kernel 108 | """ 109 | current_kernel = Kernel(url, kernel_version) 110 | current_kernel.set_config(config) 111 | current_kernel.set_main_patch(main_patch) 112 | current_kernel.send_files(incremental_patch_names_list) 113 | current_kernel.get_livepatch() 114 | -------------------------------------------------------------------------------- /src/elivepatch_client/log.py: -------------------------------------------------------------------------------- 1 | # Copyright 2003-2018 Gentoo Foundation 2 | # Distributed under the terms of the GNU General Public License v2 3 | 4 | """Logging related code (taken from Gentoo Catalyst) 5 | This largely exposes the same interface as the logging module except we add 6 | another level "notice" between warning & info, and all output goes through 7 | the "elivepatch" logger. 8 | """ 9 | 10 | from __future__ import print_function 11 | 12 | import logging 13 | import logging.handlers 14 | import os 15 | import sys 16 | import time 17 | 18 | 19 | class elivepatchLogger(logging.Logger): 20 | """Override the _log member to autosplit on new lines""" 21 | 22 | def _log(self, level, msg, args, **kwargs): 23 | """If given a multiline message, split it""" 24 | # We have to interpolate it first in case they spread things out 25 | # over multiple lines like: Bad Thing:\n%s\nGoodbye! 26 | try: 27 | msg %= args 28 | for line in msg.splitlines(): 29 | super(elivepatchLogger, self)._log(level, line, (), **kwargs) 30 | except: 31 | print("msg") 32 | print(msg) 33 | print("args") 34 | print(args) 35 | 36 | 37 | # The logger that all output should go through. 38 | # This is ugly because we want to not perturb the logging module state. 39 | _klass = logging.getLoggerClass() 40 | logging.setLoggerClass(elivepatchLogger) 41 | logger = logging.getLogger('elivepatch') 42 | logging.setLoggerClass(_klass) 43 | del _klass 44 | 45 | 46 | # Set the notice level between warning and info. 47 | NOTICE = (logging.WARNING + logging.INFO) // 2 48 | logging.addLevelName(NOTICE, 'NOTICE') 49 | 50 | 51 | # The API we expose to consumers. 52 | def notice(msg, *args, **kwargs): 53 | """Log a notice message""" 54 | logger.log(NOTICE, msg, *args, **kwargs) 55 | 56 | def critical(msg, *args, **kwargs): 57 | """Log a critical message and then exit""" 58 | status = kwargs.pop('status', 1) 59 | logger.critical(msg, *args, **kwargs) 60 | sys.exit(status) 61 | 62 | error = logger.error 63 | warning = logger.warning 64 | info = logger.info 65 | debug = logger.debug 66 | 67 | 68 | class elivepatchFormatter(logging.Formatter): 69 | """Mark bad messages with colors automatically""" 70 | 71 | _COLORS = { 72 | 'CRITICAL': '\033[1;35m', 73 | 'ERROR': '\033[1;31m', 74 | 'WARNING': '\033[1;33m', 75 | 'DEBUG': '\033[1;34m', 76 | } 77 | _NORMAL = '\033[0m' 78 | 79 | @staticmethod 80 | def detect_color(): 81 | """Figure out whether the runtime env wants color""" 82 | if 'NOCOLOR' is os.environ: 83 | return False 84 | return os.isatty(sys.stdout.fileno()) 85 | 86 | def __init__(self, *args, **kwargs): 87 | """Initialize""" 88 | color = kwargs.pop('color', None) 89 | if color is None: 90 | color = self.detect_color() 91 | if not color: 92 | self._COLORS = {} 93 | 94 | super(elivepatchFormatter, self).__init__(*args, **kwargs) 95 | 96 | def format(self, record, **kwargs): 97 | """Format the |record| with our color settings""" 98 | msg = super(elivepatchFormatter, self).format(record, **kwargs) 99 | color = self._COLORS.get(record.levelname) 100 | if color: 101 | return color + msg + self._NORMAL 102 | else: 103 | return msg 104 | 105 | 106 | # We define |debug| in global scope so people can call log.debug(), but it 107 | # makes the linter complain when we have a |debug| keyword. Since we don't 108 | # use that func in here, it's not a problem, so silence the warning. 109 | # pylint: disable=redefined-outer-name 110 | def setup_logging(level, output=None, debug=False, color=None): 111 | """Initialize the logging module using the |level| level""" 112 | # The incoming level will be things like "info", but setLevel wants 113 | # the numeric constant. Convert it here. 114 | try: 115 | level = logging.getLevelName(level.upper()) 116 | 117 | # The good stuff. 118 | fmt = '%(asctime)s: %(levelname)-8s: ' 119 | if debug: 120 | fmt += '%(filename)s:%(funcName)s: ' 121 | fmt += '%(message)s' 122 | 123 | # Figure out where to send the log output. 124 | if output is None: 125 | handler = logging.StreamHandler(stream=sys.stdout) 126 | else: 127 | handler = logging.FileHandler(output) 128 | 129 | # Use a date format that is readable by humans & machines. 130 | # Think e-mail/RFC 2822: 05 Oct 2013 18:58:50 EST 131 | tzname = time.strftime('%Z', time.localtime()) 132 | datefmt = '%d %b %Y %H:%M:%S ' + tzname 133 | formatter = elivepatchFormatter(fmt, datefmt, color=color) 134 | handler.setFormatter(formatter) 135 | 136 | logger.addHandler(handler) 137 | logger.setLevel(level) 138 | except: 139 | pass 140 | -------------------------------------------------------------------------------- /src/elivepatch_client/patch.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import tempfile 4 | import subprocess 5 | from elivepatch_client import log 6 | 7 | 8 | class ManaGer(object): 9 | 10 | def __init__(self): 11 | self.tmp_patch_folder = os.path.join('/tmp', 'elivepatch') 12 | if not os.path.exists(self.tmp_patch_folder): 13 | os.mkdir(self.tmp_patch_folder) 14 | 15 | def list(self, kernel_version): 16 | """ 17 | Listing incremental patches 18 | :param kernel_version: String with kernel version 19 | :return: list of incremental patch 20 | """ 21 | kernel_sources = 'gentoo-sources' 22 | patch_filename = [] 23 | previous_patches = [] 24 | # search eapply_user patches 25 | # local basedir=${PORTAGE_CONFIGROOT%/}/etc/portage/patches 26 | try: 27 | portage_configroot = os.environ['PORTAGE_CONFIGROOT'] 28 | except: 29 | portage_configroot = os.path.join('/etc', 'portage', 'patches') 30 | kernel_patch_basedir_PN = os.path.join(portage_configroot, 'sys-kernel', 31 | kernel_sources) 32 | kernel_patch_basedir_P = os.path.join(portage_configroot, 'sys-kernel', 33 | kernel_sources + '-' + kernel_version) 34 | basedir = [kernel_patch_basedir_PN, kernel_patch_basedir_P] 35 | for path in basedir: 36 | for (dirpath, dirnames, filenames) in os.walk(path): 37 | if filenames and not dirnames: 38 | for filename in filenames: 39 | if filename.endswith('.patch'): 40 | log.notice('dirpath: '+str(dirpath),'filename: '+str(filename)) 41 | incremental_patch_fullpath = os.path.join(dirpath, filename) 42 | log.notice(incremental_patch_fullpath) 43 | patch_filename.append(incremental_patch_fullpath) 44 | # os.walk() walks in random order, perform a lexical sort 45 | patch_filename.sort() 46 | 47 | # search previous livepatch patch folder 48 | for (dirpath, dirnames, filenames) in os.walk(self.tmp_patch_folder): 49 | if filenames and not dirnames: 50 | for filename in filenames: 51 | if filename.endswith('.patch'): 52 | log.notice(str('dirpath: '+str(dirpath) + 'filename: '+str(filename))) 53 | incremental_patch_fullpath = os.path.join(dirpath, filename) 54 | log.notice(incremental_patch_fullpath) 55 | previous_patches.append(incremental_patch_fullpath) 56 | # os.walk() walks in random order, perform a lexical sort 57 | previous_patches = sorted(previous_patches, key=lambda elive: int(elive.replace('/elivepatch.patch','').split('_')[1])) 58 | 59 | # Append the previous patches to the eapply_user patches list 60 | patch_filename.extend(previous_patches) 61 | 62 | log.notice('List of current patches:') 63 | return patch_filename 64 | 65 | def load(self, patch_fulldir, livepatch_fulldir): 66 | """ 67 | kpatch load live patch into the client machine working kernel 68 | :param patch_fulldir: String patch full directory 69 | :param livepatch_fulldir: String live patch full directory 70 | """ 71 | try: 72 | _command(['sudo', 'kpatch', 'load', livepatch_fulldir]) 73 | log.notice('patch_fulldir:' + str(patch_fulldir) + ' livepatch_fulldir: '+ str(livepatch_fulldir)) 74 | self._save(patch_fulldir, livepatch_fulldir) 75 | except: 76 | log.notice('failed to load the livepatch') 77 | 78 | def _save(self, patch_fulldir, livepatch_fulldir): 79 | """ 80 | Save main patch and live patch 81 | :param patch_fulldir: String patch full directory 82 | :param livepatch_fulldir: String live patch full directory 83 | """ 84 | i = 0 85 | while os.path.exists(os.path.join(self.tmp_patch_folder, "elivepatch_%s" % i)): 86 | i += 1 87 | incremental_patch_archive_directory = os.path.join(self.tmp_patch_folder, "elivepatch_%s" % i) 88 | os.mkdir(incremental_patch_archive_directory) 89 | shutil.copy(patch_fulldir, os.path.join(incremental_patch_archive_directory, "elivepatch.patch")) 90 | try: 91 | shutil.copy(livepatch_fulldir, os.path.join(incremental_patch_archive_directory, "elivepatch.ko")) 92 | except: 93 | pass 94 | 95 | 96 | def _command(bashCommand, kernel_source_dir=None, env=None): 97 | """ 98 | Popen override function 99 | 100 | :param bashCommand: List of command arguments to execute 101 | :param kernel_source_dir: the source directory of the kernel 102 | :return: void 103 | """ 104 | # Inherit the parent environment and update the private copy 105 | if env: 106 | process_env = os.environ.copy() 107 | process_env.update(env) 108 | env = process_env 109 | 110 | if kernel_source_dir: 111 | log.notice(bashCommand) 112 | process = subprocess.Popen(bashCommand, stdout=subprocess.PIPE, cwd=kernel_source_dir, env=env) 113 | output, error = process.communicate() 114 | log.notice(output) 115 | else: 116 | log.notice(bashCommand) 117 | process = subprocess.Popen(bashCommand, stdout=subprocess.PIPE, env=env) 118 | output, error = process.communicate() 119 | log.notice(output) 120 | -------------------------------------------------------------------------------- /src/elivepatch_client/restful.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # (c) 2017, Alice Ferrazzi 5 | # Distributed under the terms of the GNU General Public License v2 or later 6 | 7 | import requests 8 | import os 9 | import shutil 10 | import tempfile 11 | from elivepatch_client import patch 12 | from elivepatch_client import log 13 | import sys 14 | from io import BytesIO 15 | 16 | 17 | class ManaGer(object): 18 | """ 19 | RESTful client functions 20 | """ 21 | 22 | def __init__(self, server_url, kernel_version, uuid): 23 | self.server_url = server_url 24 | self.kernel_version = kernel_version 25 | # universally unique identifier for support multiple request 26 | self.uuid = uuid 27 | 28 | def set_uuid(self, uuid): 29 | self.uuid = uuid 30 | 31 | def set_kernel_version(self, kernel_version): 32 | self.kernel_version = kernel_version 33 | 34 | def get_kernel_version(self): 35 | return self.kernel_version 36 | 37 | def get_uuid(self): 38 | return self.uuid 39 | 40 | def version(self): 41 | """ 42 | Function for as the server version and print on screen 43 | """ 44 | url = self.server_url + '/elivepatch/api/v1.0/agent' 45 | r = requests.get(url) 46 | log.notice(r.json()) 47 | 48 | def send_files(self, temporary_config, new_patch_fullpath, incremental_patches, api): 49 | """ 50 | Function for send files and build live patch (server side) 51 | :param temporary_config: configuration file full path 52 | :param new_patch_fullpath: main patch full path 53 | :param incremental_patches: List with incremental patches paths 54 | :param api: RESTFul server path 55 | :return: json with response 56 | """ 57 | url = self.server_url+ api 58 | # we are sending the file and the UUID 59 | # The server is dividing user by UUID 60 | # UUID is generated with python UUID 61 | # TODO: add the UUID in the json location instead of headers 62 | response_dict = None 63 | headers = { 64 | 'KernelVersion' : self.kernel_version, 65 | 'UUID': self.uuid 66 | } 67 | # Static patch and config filename 68 | files=[] 69 | counter = 0 70 | log.notice('incremental_patches: '+str(incremental_patches)) 71 | for incremental_patch_fullpath in incremental_patches: 72 | if incremental_patch_fullpath.endswith('.patch'): 73 | # TODO: we need to close what we open 74 | read_incremental_patch = open(incremental_patch_fullpath, 'rb') 75 | files.append(('patch', (str(counter).zfill(5) + '.patch', read_incremental_patch, 'multipart/form-data', {'Expires': '0'}))) 76 | counter += 1 77 | files.append(('main_patch', ('main.patch', open(new_patch_fullpath, 'rb'), 'multipart/form-data', {'Expires': '0'}))) 78 | files.append(('config', ('config', open(temporary_config.name, 'rb'), 'multipart/form-data', {'Expires': '0'}))) 79 | log.notice(str(files)) 80 | try: 81 | response = requests.post(url, files=files, headers=headers) 82 | log.notice('send file: ' + str(response.json())) 83 | response_dict = response.json() 84 | except requests.exceptions.ConnectionError as e: 85 | log.notice('connection error: %s' % e) 86 | temporary_config.close() 87 | except: 88 | self._catching_exceptions_exit(self.send_files) 89 | temporary_config.close() 90 | return response_dict 91 | 92 | def get_livepatch(self, patch_folder): 93 | """ 94 | Save the patch in the incremental patches folder and install the livepatch 95 | :param patch_folder: Main patch that will be saved in the incremental patches folder. 96 | """ 97 | patch_manager = patch.ManaGer() 98 | url = self.server_url+'/elivepatch/api/v1.0/send_livepatch' 99 | payload = { 100 | 'KernelVersion': self.kernel_version, 101 | 'UUID' : self.uuid 102 | } 103 | try: 104 | r = requests.get(url, json=payload) 105 | if r.status_code == requests.codes.ok: # livepatch returned ok 106 | try: 107 | b= BytesIO(r.content) 108 | with open('myfile.ko', 'wb') as out: 109 | out.write(r.content) 110 | r.close() 111 | log.notice(b) 112 | except: 113 | log.notice('livepatch not found') 114 | r.close() 115 | except: 116 | self._catching_exceptions_exit(self.get_livepatch) 117 | 118 | with tempfile.TemporaryDirectory() as elivepatch_uuid_dir: 119 | livepatch_fulldir = os.path.join(elivepatch_uuid_dir, 'livepatch.ko') 120 | if os.path.exists('myfile.ko'): 121 | if not os.path.exists(elivepatch_uuid_dir): 122 | os.makedirs(elivepatch_uuid_dir) 123 | shutil.copy("myfile.ko", livepatch_fulldir) 124 | log.notice('livepatch saved in ' + elivepatch_uuid_dir + '/ folder') 125 | patch_manager.load(patch_folder, livepatch_fulldir) 126 | else: 127 | log.notice('livepatch not received') 128 | 129 | def _catching_exceptions_exit(self, current_function): 130 | e = sys.exc_info() 131 | log.error( "Error %s: %s" % (current_function.__name__, str(e)) ) 132 | sys.exit(1) 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------