INTRODUCTION
Computer Security and Intrusion Detection
Computer security can be broadly defined as a system that reliably behaves according to its expected functions, as outlined by Garfinkel and Spafford This expected behavior is encapsulated in the system's security policy, which sets the objectives the system must achieve A more specific definition of computer security focuses on three key principles: confidentiality, integrity, and availability Confidentiality ensures that information is only accessible to authorized users, integrity guarantees that data remains unchanged unless by authorized actions, and availability ensures that the system operates effectively, providing necessary resources to users without interruption.
Intrusion, as defined by Heady et al [HLMM91], refers to actions aimed at undermining the integrity, confidentiality, or availability of a resource In simpler terms, it represents a breach of the security policies established for systems.
Access control mechanisms serve as the primary defense for computer systems, typically represented by an access matrix that outlines policy enforcement In this matrix, columns denote the operations permitted on protected objects or services, while rows identify clients Each client has a complete description of their allowed operations, and conversely, each operation can be linked to the clients authorized to perform it Despite the conceptual simplicity of the access matrix, it is important to note that access control alone cannot prevent unauthorized information flow within the system, as such flow may occur through legitimate access to objects.
Controlling information flow is essential for enhancing security, and this can be achieved through models like the Bell and LaPadula model for maintaining secrecy and the Biba model for ensuring integrity However, implementing these security measures often compromises convenience, as both models impose restrictions on read and write operations Consequently, while aiming for a completely secure system, the practical utility of such a system may be diminished.
Access control and protection models are ineffective against internal threats and vulnerabilities in authentication systems For instance, if a weak password is compromised, access control fails to safeguard sensitive information that the user was permitted to access Additionally, security weaknesses often arise from poor design or insufficient testing of system software.
Intrusion detection systems play a crucial role in cybersecurity by identifying both successful security breaches and attempts to breach security, enabling timely countermeasures Unlike access control, these systems can monitor hacking attempts, such as password guessing, allowing for swift response actions Ultimately, intrusion detection serves as the last line of defense against cyber attacks, ensuring robust protection for sensitive information.
Our Approach
Intrusion detection is traditionally categorized into two types: anomaly intrusion detection and misuse intrusion detection Anomaly detection identifies intrusions based on unusual behavior and resource usage, such as a user accessing their account at midnight when they typically only use it during office hours This method aims to recognize typical usage patterns and flag deviations as potential intrusions, offering the advantage of not requiring specific knowledge of security vulnerabilities However, setting appropriate thresholds for what constitutes anomalous behavior can be challenging, as some abnormal activities may not indicate a true intrusion, and sophisticated hackers may exhibit behaviors that closely resemble normal usage.
Misuse intrusion detection identifies attacks that adhere to specific patterns exploiting vulnerabilities in system and application software By establishing predefined signatures of known intrusions, this method ensures effective detection However, it falls short in recognizing new, unknown attacks, as it relies on previously documented patterns that cannot account for unforeseen threats.
Our approach is based on high-level specifications that define the expected security behaviors of processes By capturing the normal behaviors, we can identify deviations that signal potential intrusions This method allows for the detection of attacks, even if they have not been previously encountered.
Damage from attacks occurs through system calls made by the compromised process to the operating system File and network operations are managed via these system calls, allowing us to represent security-related behaviors based on them By intercepting and validating these system calls in real-time, we can identify deviations from expected behaviors, enabling us to detect potential issues before they result in damage, thereby providing a preventive security measure.
We have developed a high-level language named Auditing Specification Language (ASL) to define security-related behaviors effectively ASL is capable of articulating various integrity constraints and temporal behaviors that can extend across multiple processes Specifications written in ASL are transformed into optimized C++ programs, enabling the efficient identification of any deviations from these defined specifications.
We utilize runtime enforcement techniques to guarantee that processes adhere to the behavior defined in ASL Upon detecting any deviations from this specified behavior, we can initiate automatic corrective actions to mitigate potential damage, all of which are outlined within ASL.
Key Contributions
The key innovations of our approach include:
ASL is a high-level language designed to simplify the specification of normal process behaviors, focusing on the relationships and constraints necessary for a correctly functioning system It allows users to define these conditions without delving into the complexities of how they can be verified.
Efficient runtime intrusion detection techniques utilize a compiler that translates ASL specifications into an Extended Finite-State Automaton (EFSA) This advanced model, akin to a finite-state automaton, incorporates a set of state variables, enabling it to effectively identify intrusions during runtime.
We propose the implementation of atomic execution in our ASL specification to effectively address race condition attacks This approach ensures data integrity in multi-process systems, allowing users to define the expected behavior of programs requiring atomic execution Additionally, it aids in identifying unknown intrusions related to race conditions and other synchronization errors.
Thesis Organization
This thesis is structured into several chapters: Chapter 2 provides an overview of our approach and its connection to previous research, while Chapter 3 outlines our ASL language In Chapter 4, we detail our method for atomic execution, followed by an in-depth discussion of the ASL compiler in Chapter 5, which includes examples of C++ programs it generates The concluding results are presented in Chapter 6.
OVERALL APPROACH AND RELATED WORK
Behavioral Specifications Model
Our detection system monitors individual processes by analyzing observable events at the process level, focusing on those that affect security-related behaviors While ideally, programs would be designed to detect and report security events to an external system, most current applications lack this capability Consequently, we employ alternative methods to extract relevant security-related events for effective monitoring.
identify the well-defined interfaces used by all processes,
treat interactions on these interfaces as events,
develop behavioral specifications describing permissible event sequences, and intercept and verify actual event sequences occurring at runtime against the behavioral specifications
We will focus on the interfaces between processes and their host operating systems, as these interactions are crucial for security Well-designed operating systems manage security-related operations, such as file and network access, through clearly defined system calls By outlining the permissible sequences of system calls for a process and monitoring these calls in real-time, we can effectively detect and prevent potential attacks before they cause harm.
Our per-process, system-call-based behavioral specifications are decomposed into two types:
local correctness specifications that involve reasoning about the actions of the process in isolation, and
non-interference specifications, which ensure that the concurrent actions of other processes do not interfere with the correct operation of the process
Local correctness specifications define patterns in the sequences of system calls and their arguments executed by a process, while non-interference specifications focus on atomic sequences of these system calls A detailed discussion on these concepts will be presented in Chapter 4.
Local correctness specifications are straightforward to grasp, as demonstrated by an example involving a program, V, that contains a vulnerability allowing unauthorized access to the password file, /etc/password To address this issue, we create a specification, M, which identifies a specific sequence of system calls: open() followed by write() This specification ensures that both system calls target the sensitive file, /etc/password, thereby highlighting the security flaw in the program.
To understand non-interference, we examine race condition attacks in a privileged program, V, running as process P This program allows user-specified log file creation while needing to check user permissions However, since P operates as root, the open() system call bypasses the necessary permission checks To address this, V utilizes the access() system call to verify the real user's write permission before executing open() While this logic appears correct under non-interleaved operations, it becomes flawed if another process alters the log file object between the two calls To ensure correct functionality, it is crucial that these system calls are executed without interference from other processes This is achieved by placing the data accessed by both calls in an atomic sequence, allowing M to detect any modifications by other processes and respond appropriately to prevent data corruption.
The decomposition of behavioral specifications into local and non-interference components allows for the implementation of independent system call monitoring objects for each process, enhancing local security behavior To effectively detect interference, coordination among detection engines across different processes is essential We propose to implement the non-interference component in a decentralized manner within the runtime infrastructure to efficiently intercept system calls.
Detection System Model
The detection system comprises two key components: an offline system responsible for creating detection engines based on ASL behavioral specifications, and a runtime system that executes these generated engines.
The offline production of the system call detection engine involves a system security administrator who utilizes ASL to identify security-related behaviors in a program (P) This process relies on three primary sources: the program's source code, its documentation outlining intended behaviors, and relevant attack advisories The identified behaviors and vulnerabilities are encapsulated in ASL specifications, referred to as the monitor (M) for program P The ASL compiler then converts M into a C++ class definition (C), which is subsequently compiled and linked with the runtime infrastructure to create the detection engine.
Figure 1 - Offline system for production of detection engines
Manual or document that describe the intended behavior
Attack advisories or mailing lists
The C++ Compiler offers essential support functions related to the monitored interface as specified It includes a system call runtime infrastructure that enables the interception of system calls, routes them to the detection engine, and supplies functions for the detection engine to execute responsive actions effectively.
The system-call detection engine, created during an offline process, is utilized at runtime with one instance per process A single system-call interceptor monitors each process, as illustrated in Figure 2 In this example, process P i (Q i) is under surveillance using the object M p (M q), which is derived from the monitoring specification for program P (Q) For clarity, we designate i as the process ID, highlighting the system calls executed by the monitored process.
The system call interceptor plays a crucial role in monitoring system calls made by processes P i (Q i) as they transition between the user space and the operating system kernel It captures these calls right before they are dispatched to the kernel and immediately after the return value is received This functionality supports the system call detection engine's infrastructure, allowing it to identify unexpected sequences of system calls initiated by P i (Q i) and make necessary modifications.
P i 's ( Q i 's )use of system calls to prevent detected deviations from causing damage
Figure 2 - Runtime system for execution of detection engines
Operating System System Call Interceptor
Related Work
Intrusion detection techniques are primarily categorized into misuse intrusion detection and anomaly intrusion detection Misuse intrusion detection focuses on identifying known intrusion patterns, referred to as intrusion signatures Porras & Ilgun introduced this concept using State Transition Analysis to represent computer penetrations as sequences of state changes leading to a compromised system Additionally, Kumar's work employs pattern matching for intrusion detection, classifying known security exploits based on time complexity for vulnerability detection, and utilizes a single computational model for monitoring these exploitations through pattern matching techniques.
Anomaly detection identifies attacks by recognizing deviations from established normal behavior within a system This method involves comparing current activities to predefined norms Forrest's research demonstrates this concept by defining normal behavior for UNIX processes based on the sequence of system calls made during typical operations Intrusions are flagged when unfamiliar system call sequences, not previously recorded during normal use, are detected.
Our approach utilizes specification-based detection for intrusion detection, a method introduced by Ko This technique focuses on monitoring program behavior against predefined specifications, allowing for the identification of deviations rather than relying on specific attack patterns Unlike misuse detection, which depends on known attack signatures, specification-based detection can uncover unknown threats Additionally, it offers a more defined threshold for distinguishing between normal behavior and potential intrusions compared to anomaly detection.
Intrusions often follow specific attack patterns that exploit system and application software vulnerabilities, which can be pre-defined and encoded to identify variations in these activities This allows for immediate flagging of potential intrusions upon detection of such events Misuse detection techniques, including state-transition systems and pattern-matching, effectively address known vulnerabilities; however, they face challenges when dealing with unknown vulnerabilities.
Ilgun developed USTAT, a real-time intrusion detection tool for UNIX that identifies penetrations as sequences of state changes using state transition diagrams These diagrams consist of nodes representing states and arcs depicting actions Our approach enhances USTAT by employing an extended finite-state automaton (EFSA), which offers a more powerful representation than traditional state transition diagrams Unlike USTAT, which analyzes static audit data, our system intercepts real-time system call sequences, allowing for dynamic data analysis Consequently, while USTAT is limited to intrusion detection, our system also incorporates prevention capabilities.
Kumar proposed a novel method for misuse intrusion detection by encoding intrusion signatures into a structured representation of low-level system events associated with attacks His classification system analyzes the structural interrelationships among observable system events, enabling the formal detection of specific exploitations based on their effects within the system event trace This approach allows for discussions about intrusion signatures within distinct categories, rather than focusing solely on the vulnerabilities that lead to intrusions.
They create computational models to identify intrusions by leveraging classifications that highlight the interrelationships of event signatures within each category This approach enables efficient matching of relevant signatures, leading to the development of a justified computational model that effectively represents and matches intrusions from their established classifications.
Our approach to pattern specification focuses on defining what needs to be matched, rather than how it is matched, allowing for a clean separation of the matching algorithm from the specification Similar to Kumar's method, we utilize patterns to specify security-related system behavior, but with a key difference - our patterns describe normal behavior, enabling the detection of unknown attacks.
Anomaly detection is a method used to identify unusual behavior by first establishing a profile of normal activities and then flagging any deviations as potentially intrusive This approach employs various techniques, including statistical methods, expert systems, and neural networks, to create a comprehensive understanding of acceptable behavior The key advantage of anomaly detection is its ability to automatically recognize significant deviations from established norms, allowing for the identification of unknown intrusions However, a notable drawback is that attackers may evade detection by gradually altering their behavior over time.
Forrest et al developed an innovative intrusion detection technique inspired by animal immune systems, focusing on UNIX processes They define "self" through short sequences of system calls typical of normal operations Intrusions are identified by detecting "foreign" system call sequences not present in the established normal behavior database This database, specific to each process, allows for continuous monitoring of ongoing behavior By comparing current system call sequences against the established normal patterns, any deviations can be flagged as anomalies, indicating potential security threats.
Their research findings align with ours, emphasizing the learning of normal process behaviors, while our emphasis lies in the efficient specification and enforcement of these behaviors Notably, the finite-state automaton developed through the technique of [Kosoresow97] can be integrated as input into our runtime monitoring system.
A specification-based approach, initially introduced by Ko et al., addresses the limitations of misuse detection by outlining the intended behaviors of programs, eliminating the need to identify all potential vulnerabilities This method involves creating security specifications for privileged programs that define their desirable behaviors, guided by the program's functionality and the overarching system security policy.
Ko presents a formal intrusion-detection model that utilizes traces—ordered sequences of execution events—to define the intended behavior of programs This model employs a formal specification language to efficiently express valid operation sequences for one or more monitored subjects Any sequence of operations performed by the subject that deviates from this specification is deemed a security violation, referred to as a trace policy.
Parallel environment grammars (PE-grammars) are utilized to define trace policies effectively These grammars facilitate the parsing of audit trails and can articulate various trace policy classes crucial for security Consequently, the parsing of audit trails serves as a detection mechanism within a specification-based detection system, identifying operations by subjects that violate established trace policies.
Our approach significantly enhances security by enforcing specified behaviors in real-time to thwart attacks, unlike Ko's method, which relies on offline analysis of audit logs Additionally, a key difference lies in the specification language employed in our system.
Benefits of Our Approach
Our approach improves on previous work greatly with respect to the following three aspects:
1 A high-level language that simplifies specification of normal behaviors of processes ASL is intended to simplify the specification of relationships and constraints that must hold in a correct system, without being concerned about the detail as how these conditions can be verified When users write specifications, they can be concerned only about the correct condition instead of the implementation This feature greatly simplifies the user's work. Compared with the PE-grammar used in Ko's work, use of regular grammar implies that we can potentially translate all the specifications into one single EFSA.
2 Efficient techniques for runtime intrusion detection Our compiler translates ASL specifications into an Extended Finite-State Automaton (EFSA) An EFSA is similar to a finite-state automaton with a set of state variables The EFSA can be simulated at runtime to detect intrusions efficiently.
3 Atomic execution to detect race condition attacks We introduce atomic execution in our
The ASL specification effectively addresses race condition attacks by enabling atomic execution, which ensures data integrity in multi-process systems This feature allows users to define the expected behavior of programs requiring atomic execution Unlike Ko's approach, which relies on intrusion signatures to detect race condition attacks, our method focuses on specifying normal program behavior, thus enabling the detection of unknown intrusions This highlights a key advantage of the specification-based approach over traditional misuse detection methods.
ASL SPECIFICATION LANGUAGE
External Functions
External functions are defined outside detection engines but can be accessed by them, serving primarily to invoke support functions essential for detection engine operations and system call reactions For example, when a detection engine receives an event related to file access, it may need to resolve symbolic links and references to process the event effectively.
“.” and “ ” in the file name to obtain a canonical name for file It may make use of a support function declared as follows to accomplish this: string realpath(CString s);
The detection engine may also need to check access permissions associated with the file, which may be done using a support function declared as follows:
In ASL, system call references are found in two distinct contexts: as part of an event and within an ASL specification To clearly differentiate these contexts, we adopt the convention of using an @-symbol before system calls that appear in the ASL specification.
Events
We represent the behavior of the protected system through a sequence of events, specifically focusing on the process level where events correspond to the invocation and return of system calls Each event is characterized by the format e(a1, , an), where e signifies the event name and a1, , an represent the event's arguments.
In system calls, we identify two key events: the entry into the system call and the exit from it For instance, a typical declaration for a system call entry event can be represented as: event stat(CString s, StatBuf b);
The exit from this system call is denoted by: event $stat(CString s, StatBuf b);
In our system, we denote entry events with the system call name and use a $-symbol prefix for exit events However, this method does not allow direct access to the return value of completed system calls or the errno value, which is a global variable in UNIX-based systems that holds the most recent error code To effectively retrieve these values, we propose implementing two external functions in the interface: `int rv() const;` and `int errno() const;`.
In our current implementation, each process \( P_i \) is associated with a dedicated monitoring object \( M_p \) For local correctness specifications, it is sufficient to send the system calls made by \( P_i \) directly to \( M_p \) by invoking a method on \( M_p \) that corresponds to the event name However, to address interference specifications, it is essential to deliver the system calls from all processes running on the host to an interference detector, which is integrated into the runtime infrastructure for system call detection.
Patterns
ASL general event patterns define acceptable and unacceptable behaviors through a combination of primitive patterns and temporal operators These primitive patterns are constructed from atomic patterns and focus on particular events of interest Temporal operators play a crucial role in establishing the sequencing and timing relationships that govern the interactions between these primitive events.
An atomic pattern is structured as e(a₁, , aₙ) | C, where e represents an event and C is a boolean expression involving a₁, , aₙ This expression can include standard arithmetic, comparison, and logical operations, as well as comparisons like x = expr, where x is a new variable In this context, the purpose of such comparisons is to assign the value of expr to the variable x.
A primitive pattern is created by combining atomic patterns using the disjunction operator (||) and may also include the complement operator (!) For instance, a primitive pattern can be illustrated with the example: execve(f,x,y) || realpath(f) != "/usr/ucb/finger".
The example highlights all instances of the execve() system call, specifically excluding the execution of /usr/ucb/finger In this context, the external function realpath is utilized to resolve all types of links, including hard and symbolic links, as well as any instances of “.” and “ ” in the filename argument, ultimately returning an absolute path name This approach can effectively capture relevant execution patterns.
"Internet worm" attack that exploited fingerd vulnerabilities [Spafford91] Another example of a primitive pattern is
!((open(f)|realpath(f)=/home/*/.plan)||(close(f))||(exit(f))
This example highlights the interception of all system calls except for those related to opening ".plan" files, closing files, or terminating processes Such patterns can be effectively utilized to monitor and restrict unauthorized system calls across various processes.
ASL employs various temporal operators to represent sequencing and timing relationships between events, allowing primitive event patterns to be combined into more intricate general event patterns.
Sequential composition: p 1 ; p 2denotes the event pattern p 1 followed by the event pattern p 2
Alternation: p 1 || p 2 denotes the occurrence of either p 1 or p 2.
Repetition: p* denotes 0 or more occurrence of p
Atomicity: nonatomic (d , p) corresponds to an occurrence of pattern p within which the data item d is not accessed atomically
For convenience, we define the operator “ ” that can be applied only to primitive patterns p 1 p 2 is equivalent to p 1 ; (! ( p 1 || p 2 ) ); p 2 , i.e., p 1 followed by p 2 with possibly other events occurring in between
To avoid excessive use of parenthesis, we define the following associatively and precedence for the temporal operators The operators “;” and “||” associate to the left, while
“ ” is non-associative The operator “!” has the highest precedence, “*” has the next lower precedence, “;” has the next lower precedence and “||” has the lowest precedence.
Rules
A rule is structured as pat → reaction, where "pat" represents a defined pattern and "reaction" outlines the sequence of steps triggered by the pattern's occurrence Actions within this framework can include empty actions, variable assignments, or calls to external functions Empty actions result in no effect, while assignment actions update a variable within a module External function invocations execute specified functions through the runtime infrastructure, allowing the detection engine to read or write data in the monitored process or perform system calls as needed.
Event Abstractions
Event abstraction is a powerful tool that enables programmers to define and manage complex event patterns as if they were simple, primitive events This mechanism simplifies the writing of behavioral specifications by allowing similar UNIX system calls, which often have overlapping functionalities, to be grouped together For example, both the creat() and open() system calls serve the purpose of opening new files, leading to the creation of an abstract event called writeOpen By using writeOpen in a single behavioral specification, programmers can effectively monitor processes that utilize either creat() or open() to open new files, streamlining the development process.
Code Example 1 - Definition of writeOpen() Abstract Class event writeOpen(path) = open(path, flags)|(flags & (O_WRONLY | O_APPEND | O_TRUNC)) || open(path, flags, mode)|(flags & (O_WRONLY | O_APPEND | O_TRUNC)) || creat(path, mode);
In various contexts, different levels of abstraction may be necessary, leading to overlaps among user-defined events For example, an abstract call could represent readOpen, while another might encompass all open actions, whether for reading or writing This establishes a hierarchy where individual system calls form the base level, followed by readOpen and writeOpen, and culminating in the Open_all() function at a higher level Additionally, the hierarchy may include other levels that enable the treatment of file openings similarly to socket openings or connections A comprehensive list of event abstractions for Red Hat Linux system calls can be found in Appendix A.
The `readOpen()` function is defined to open a file at a specified path with given flags, supporting both read-only and additional mode options Similarly, the `Open_all()` function combines the functionalities of `readOpen()` and `writeOpen()` to manage file access efficiently.
Example Specifications
In Code Example 3, the cat program is designed with specifications that prevent it from accessing the password file by disallowing the execution of the offending system call, which results in an error code being returned.
Code Example 3 - ASL Monitoring Specification Preventing cat From Opening /etc/passwd class CString {
String get() const; void set(String s);
} string realpath(const CString s); event open(const CString f, int flags, mode_t mode); main() { open(f, fl, m)|realpath(f) = ”/etc/passwd” -> fail(-1,ENOPERM);
The following example illustrates how to defend against race condition vulnerabilities We begin by describing a program, R, which possesses a race vulnerability As a setuid to root program, executing R sets its effective user ID to root In most system calls, access to system resources is determined by the permissions of the effective user, highlighting the importance of secure programming practices.
R has the privilege of root access, even if the actual user invoking R is not root When R allows the user to specify a log file, L, it must ensure that L is writable by the real user, not the effective user, to prevent unauthorized access to protected files like /etc/passwd The open() system call checks permissions based on the effective user, making it inadequate for this purpose Therefore, R first uses the access() system call, which verifies permissions based on the real user If access() confirms write permission, R proceeds to open L, assuming that access() has validated the real user's rights However, external changes to L between the access() and open() calls can create a race condition, allowing an attacker to manipulate L into a protected file, thus exploiting the vulnerability This can occur if the attacker removes L and creates a symbolic link to a file that only root can write to, enabling unauthorized data writing by R.
There are several ways to protect against such attacks in ASL The first approach is captured by the following specification in Code Example 4
The code example demonstrates the ASL Monitoring Specification rProg1, focusing on race vulnerabilities with functions such as `realpath`, `open`, and `access` It includes event declarations for file operations and user ID management, specifically `@getuid()` and `setreuid()` The `main` function showcases a conditional access check, verifying if the real user ID matches the effective user ID after resolving the file path.
open((name1, flags, mode1)|(rn==realpath(name1) { changedEuid=1; savedEuid= @ getuid();
$open(f,flag,mode) | (changedEuid==1) { changedEuid=0;
The specification operates by temporarily setting the effective user ID of a calling process to its real user ID whenever an open system call follows an access system call on the same file This adjustment occurs before the OS kernel executes the open call, and the current effective user ID value is saved beforehand.
The effective user ID is temporarily reset and stored in the state variable savedEuid, with a flag changedEuid indicating this change Upon completion of the open system call, the original effective user IDs are restored using the values saved in these state variables.
The second defense against race vulnerabilities employs atomic sequences, which will be explored in detail in the following chapter The atomic sequence language (ASL) can be expressed as: nonatomic (f.target), (access(f, md) writeOpen(f)) → fail(-1, EACCESS).
ATOMIC EXECUTION…
Atomic Execution
To address race condition issues, executing system calls atomically is essential This approach focuses on achieving apparent atomicity, which allows for interleaved system calls as long as the final outcome remains consistent with a scenario where no interleaving occurred Apparent atomicity is maintained by ensuring that the data accessed during an atomic sequence remains unchanged by external system calls until the sequence concludes To safeguard the readset from external corruption, the operating system can track the readset for each atomic sequence and check the writeCalls of other system calls against it If there is any overlap, it indicates potential corruption of the readset Therefore, implementing a readset corruption detector requires identifying the readset and writeCalls for each system call.
Defining Readset/Writeset
In our specification language, we explicitly define the data that requires protection, as there are significantly fewer data types than system calls Instead of outlining the readset and writeset for each system call, we specify the system calls associated with reading and writing each data type The readset and writeset of a system call encompass the relevant data, while the readCalls and writeCalls of a data type list the corresponding system calls Common data types include file content, file ownership, and file permissions.
Implementation Approach
In atomic access, the term "object" refers to the entity being accessed, such as a file's permissions as one object and its content as another To ensure that the content of file f remains unchanged within the specified pattern E, we can establish the specification: nonatomic(f.target, E) → fail.
In the context of file operations, the content of the file serves as the object, while process P1 and monitor M1 are involved in managing access Atomic execution of f.target within pattern E can be disrupted in two ways: firstly, if another process opens file f before P1 reaches E, or secondly, if the file is accessed during P1's execution.
To monitor the integrity of file f during concurrent access, we implement two key data structures: a counter for incomplete write operations and a unique lock identified by a sequence number (SN) and a boolean Brokenflag When process P1 encounters the specified event E, it establishes a lock on file f For any other process to write to file f, this lock must be released, which sets the Brokenflag to true, indicating that the atomicity requirement for file f has been compromised.
To detect a process's intent to write an object, we define a set of
IncompleteWriteOperations and CompleteWriteOperations are crucial for managing object states during write processes An IncompleteWriteOperation signifies that the object is pending a write, which may disrupt any existing locks, while a CompleteWriteOperation confirms that the write has been finalized, ensuring future locks are secure For instance, in the case of a file's content, the IncompleteWriteOperation involves a WriteOpen() system call, whereas the CompleteWriteOperation is represented by a close() system call Conversely, for a file's permissions, the IncompleteWriteOperation is absent since the chmod() system call autonomously completes the write process.
Each object in the system is linked to a writeCall that encompasses all the system calls responsible for modifying that object For instance, when the object pertains to a file's content, the associated writeCall includes functions such as WriteOpen(), close(), and symlink() Conversely, if the object relates to a file's permissions, the writeCall will reflect the relevant modifications.
In a readset, conflicts are not possible; therefore, all conflicts must involve a writeset This leads us to concentrate on the writeset, where the strategy involves attaching a lock to the object that requires protection When a process P1 aims to safeguard an object within a specific pattern, it initiates the locking mechanism to ensure data integrity.
When the first event \( e_1 \) occurs, the corresponding monitor initiates a check on the object If another process has modified the object, it will return a 'fail' message If not, the monitor locks the object and notifies all other monitors in the system Upon the occurrence of the last event \( e_n \), the monitor releases the lock and informs all other monitors of this action During the locked period, any attempt by a process to write to the object will result in the lock being broken, and a 'Broken' message will be returned.
Suppose there are n processes P 1, P 2 … Pn and n corresponding monitors M 1, M 2…
Mn in our system M 1 has the specification nonatomic(Object, P)fail.
A fixed data structure associate with an object contains:
A data structure associated with an object's instance contains:
Object1.counter=0 // how many IncompleteWriteOperations on the object Object1.lock_list=null // list of locks
Code Example 5 Algorithm for Atomic Execution
Case 1: M 1 sees the first event e 1 in the pattern P broadcast begin(Object1,SN) to all M i , when M 1 receives all ACKs or time out, M 1 sends e 1 to the kernel
/*begin(Object1, SN) sets a lock on Object1 and the sequence number of this lock is SN */
Case 2: M 1 sees the last event e n in the pattern P, or M 1 cannot match pattern P.
Broadcast end(Object1, SN); if ( M 1 has matched the pattern && receives one Broken(Object1)) then (atomic requirement has been violated) take reactions
//end(Object1, SN) means M 1 wants to release the lock SN on Object1.
Case 1: receive begin(Object1,SN): if (Object1 exists) then {if (Object1.counter!= 0) then Object1.lock_list.insert ((SN, true));//lock broken else Object1.lock_list.insert ((SN, false));//not broken } else {create Object1;
Object1.lock_list.insert ((SN, false));
Case 2: receive end(Object1, SN) if ((SN, true) in Object1.lock_list) then send Broken(Obejct1) to M 1 ;//lock for Object1 has broken Object1.lock_list.delete((SN,*)); if (Original(Object1)) then destroy Object1;
Case 3: P i has an IncompleteWriteOperation(Object1) system call trapped by
M i , if (Object1 exists) then {Object1.counter ++; for (every lock in Object1.lock_list) lock.Brokenflag = true;
Case 4: M i has a CompleteWriteOperation(Object1) system call
Object.counter ; if Original(Object1) then destroy Object1;
/* Function Original(Object) return true when Object has its initial value, where counter==0 and lock_list==null */
Case 5: P i has writeCall(Object1) system call if ((Object1 exist)&&(Object1.lock_list!=null)) for(every lock in Object1.lock_list) lock.Brokenflag = true;
Some Examples of the Algorithm
To illustrate the algorithm, consider a scenario where we need to ensure that the content of file f remains unchanged between two system calls, access(f) and open(f) In this case, let process P1 interact with monitor M1 The specification can be defined as nonatomic(f.target, access(f) open(f)) → fail, indicating that the target of the file must remain consistent throughout the operations.
The fixed data structure attached with a file's content contains:
The only other process is P 2 with the monitor M 2 Let the system call sequences in M 1 and M 2 be:
When M1 accesses file f at time t1, it sends a begin(f, SN) message to M2, which then creates the object f and adds the lock (SN, false) to f.lock_list At time t2, when M2 receives a symlink(f) request, it updates f.lock_list to (SN, true) Later, at time t3, M1 sends an end(f, SN) message Upon receiving this, M2 checks its lock_list and finds (SN, true), prompting it to notify M1 with a Broken(f) message Consequently, M1 realizes that the atomic requirement has been violated.
If the system call sequences in M 1 and M 2 are:
When M 2 opens object f at time t1, it initializes f and sets its counter to 1 At time t2, M 1 accesses f and sends a begin message to M 2 Upon receiving this, M 2 recognizes that f exists and adds a lock to its lock_list When M 2 closes f at time t3, it decrements the counter to zero, but the presence of the lock prevents f from being destroyed At time t4, M 1 opens f again and sends an end message to M 2 In response, M 2 checks the lock_list, finds the existing lock, and informs M 1 that the atomic requirement has been broken.
Discussion of Correctness
The correctness of the algorithm is evaluated under the assumption that the process is P1 and the monitor is M1 There are only two scenarios that can disrupt the atomic execution of an object O within the pattern E: one occurs before P1 reaches a certain point.
E, some other process has an InCompleteWriteOperation on O The other way is when P 1 gets into E and does not go out, some other process tries to modify the object O
We argue the correctness corresponding the following two cases:
Case 1: If there is an atomicity violation, then M 1 gets a 'Broken' message
Assuming an atomicity violation occurs without a 'Broken' message, let’s consider process P1, which maintains a data object O1 in a specific pattern (e1, , en), while process P2 modifies O1 within that same pattern This scenario contradicts our initial assumption.
P 2 can change O 1 in either of the following two ways:
P2 initiates a system call through the writeCall of O1 during the specified time pattern (e1, , en) In the case of M2, due to condition 5, the lock's Brokenflag is activated, triggering a broken message to be dispatched in case 2.
The second way is that P 2 executes an InCompleteOperation to object O 1 before P 1 reaches the pattern and completed within or after the pattern In this case, for M 2 , due to case
Before P1 reaches the pattern, object O1 is created, and the counter is set to 1 When M1 receives e1, it sends a 'begin' message in case 1, while M2 sets the lock's BrokenFlag to true, triggering a 'Broken' message in case 2.
So, in both cases, a 'Broken' message will be sent which means that there is an atomicity violation.
Case 2: If M 1 gets a 'Broken' message and a pattern has been matched, then there must be an atomicity violation.
The 'Broken' message can exclusively be transmitted by M i in scenario 2, indicating that a lock has been compromised Consequently, the lock's Brokenflag is activated to true in one of three designated locations.
In scenario one, an InCompleteWriteOperation occurs on the data object before a lock is applied, allowing for potential modifications to the data either during or after the operation This sequence leads to a violation of atomicity.
In case 3, it means a lock exists first, a coming InCompleteWriteOperation can break the lock This is an atomicity violation
In case 5, it means a write operation happens within the pattern This is an atomicity violation.
TRANSLATION FROM ASL INTO AUTOMATON…
Translation Algorithm
An EFSA can be categorized as either deterministic (DEFSA) or nondeterministic (NEFSA) When considering efficiency, generating a DEFSA is generally preferred over a NEFSA However, converting a NEFSA into a DEFSA may not always be feasible, as it can lead to significant increases in space requirements.
Traditional finite state automata (FSA) can be converted into equivalent deterministic automata, typically resulting in an exponential increase in control states, which is manageable for performance-critical applications like compiler lexical analysis However, in the case of extended finite state automata (EFSA), the state explosion can be significantly larger, as it is exponential in the product of the possible values for each auxiliary state variable For example, a deterministic EFSA equivalent to a nondeterministic EFSA with a single 32-bit integer state variable can have a staggering minimum of 2^2^32 states.
In analyzing the regular expression (a|b)*a(a|b)*b, both the nondeterministic finite automaton (NFA) and deterministic finite automaton (DFA) exhibit three states When examining the extended regular expression (a|b)a(x)(a|b)*b(x), where the variable x represents 32-bits and has 2^32 possible values, the corresponding nondeterministic EFSA also contains three control states However, converting this EFSA into a deterministic EFSA requires a significant increase in states, necessitating at least 2^(2^32) states due to the need to account for all possible combinations of x.
The DEFSA for recognizing the prefix (a|b)*a(x)(a|b)* consists of 2^(2^32) distinct states, accommodating all combinations of a(1) to a(2^32) for lengths ranging from 1 to 2^32 For a string length of 1, the possible values of a(x) range from a(1) to a(2^32), resulting in C(2^32, 1) combinations When the string length is 2, the possible combinations of a(x)a(x) yield C(2^32, 2) In general, for a string length of K, the number of possible strings is represented as C(2^32, K) Therefore, the total number of distinct states is the sum of combinations from C(2^32, 1) to C(2^32, 2^32), equating to 2^(2^32).
This problem leaves us with two choices:
restrict the class of ASL patterns so that they can be compiled into DEFSA
do not convert an NEFSA into an EFSA, and simulate the NEFSA at runtime
The NEFSA runtime simulation method involves replicating the current state of a NEFSA to create a new instance when a nondeterministic transition is needed Instances that cannot proceed with future transitions are terminated, freeing up their resources Unlike DEFSA, which must account for all possible input strings, the NEFSA approach focuses solely on the strings that occur during runtime, potentially reducing space requirements significantly.
The approach outlined involves representing the transition relation of an Extended Finite State Automaton (EFSA) in code during runtime, while the current state, including state variables, is stored in data structures By integrating all patterns into a single ASL specification, only one instance of the transition relation will exist at runtime To accommodate nondeterminism, multiple instances of the dynamic state of the EFSA will be allowed, reflecting all possible states the Non-deterministic EFSA (NEFSA) could achieve based on its input up to that moment.
When an EFSA (Extended Finite State Automaton) requires a two-way nondeterministic transition for an event e, a "fork" operation is executed to replicate its current state This process allows the new instance to pursue one of the nondeterministic options, while the original instance continues with the alternate choice.
Our algorithm for generating EFSA from ASL patterns is grounded in the foundational works of Brzozowski and Berry & Sethi, which primarily focus on regular expressions and classical finite state automata (FSA) However, our approach addresses the complexities of event arguments and state variables that involve intricate data structures By integrating and enhancing these established techniques, we have created a novel algorithm that effectively generates EFSA from a specific subset of ASL specifications.
We first introduce Brzozowski's work The syntax of regular expressions over a set of
In formal language theory, the symbol 'a' represents an element within a set, while '0' denotes a regular expression that corresponds to the empty language, meaning L(0) is the empty set Conversely, '1' signifies a regular expression that represents a language containing only the empty string, with L(1) indicating the set that includes the empty string ε Additionally, L(E) refers to the language generated by a regular expression E, and we express the equivalence of two expressions as E=F.
L(E)=L(F) Using Brzozowski's notation, (E) stands for 1 if L(E) contains the empty string; otherwise, (E) stands for 0 Thus, (E)F equals F if the empty string is in L(E); otherwise,
Brzozowski's algorithm utilizes the concept of the 'derivative' of a regular expression E with respect to a symbol a, denoted as D(a, E) This derivative, E', is another regular expression where the language of E includes the string s, and the language of E' also includes the string s For instance, when calculating the derivative of the regular expression aba+bb with respect to the symbol a, the result is the expression ba Formally, the derivative of a regular expression E by a is defined through this relationship.
Automata are formed by iteratively calculating derivatives until no additional states are generated For instance, consider an automaton that accepts the expression (ab+b)*ba, which utilizes two input symbols, 'a' and 'b' Initially, the state is set to (ab+b)*ba The derivatives are computed as follows: D(a, (ab+b)*ba) results in b(ab+b)*ba, and D(b, (ab+b)*ba) yields (ab+b)*ba+a, generating two new states This process continues with D(a, b(ab+b)*ba) leading to 0, while D(b, b(ab+b)*ba) progresses further in the computation.
(ab+b)*ba, D(a, (ab+b)*ba+a) =b(ab+b)*ba+1 and D(b, (ab+b)*ba+a)= (ab+b)*ba+a.
The algorithm generates a new state represented by the expression b(ab+b)*ba+1 Upon computing the derivative with respect to 'a', we find D(a, b(ab+b)*ba+1) = 0 For 'b', the derivative yields D(b, b(ab+b)*ba+1) = (ab+b)*ba Since no new state is produced from these derivatives, the algorithm concludes its process.
Figure 6 Automaton accepting (ab+b)*ba
Brzozowski demonstrated that the derivatives of a regular expression form a finite set when considering the properties of associativity, commutativity, and idempotence of the union operation Specifically, the set {F | ∃w: F = D(w, E)} contains a limited number of equivalence classes This finding ensures that the algorithm will terminate, as it concludes when no new states can be produced.
Berry and Sethi introduced an efficient algorithm for generating an automaton from a regular expression that features distinct symbols By marking each input symbol within the regular expression, they ensure clarity and distinction, with these marks represented as subscripts.
(ab+b)*ba is (a 1 b 2 +b 3 )*b 4 a 5 Notice that a 1 and a 5 are treated as different symbols Based on Berry's method, a deterministic finite state automaton from regular expression (a 1 b 2 +b 3 )*b 4 a 5 is shown in Figure 7, where C 0 =(a 1 b 2 +b 3 )*b 4 a 5, C 1 =b 2 (a 1 b 2 +b 3 )*b 4 a 5 C 2 =(a 1 b 2 +b 3 )*b 4 a 5
C 3 =(a 1 b 2 +b 3 )*b 4 a 5 C 4 =a 5 C 5 =1 If we unmark symbols, the result will be a non-deterministic finite automaton of the original regular expression (ab+b)*ba
In our system, the fundamental structure revolves around the combination of a system call and its associated condition, rather than the system call alone We assess the condition during execution and consider the system call together with its condition as a unified entity.
Illustration of Automata Construction
Consider the Code Example 4 in section 3.6 There are four system calls in the whole system: access(CString filename, int flag), open(CString filename, int flag, mode_t mode),
$access(CString filename , int flag) and $open(CString filename, int flag, mode_t mode) To make things clearer, we ignore all the conditions here The initial state S0 is:
(access(name, mode);(access()||$open())*;open(name1, flags, mode1)||
The FirstSet of S0 is: {access(name, mode), $open(f, f1, mode) }
So, currently, the events_set is {access(name, mode), $open(f, f1, mode) }
For event access(name, mode), we first add its variables into FSM
BindingSet(FSM, access(name, mode)) FSM is: struct FSM {
CString access_name; mode_t access_mode;
Then, we get its next state S1 by computing FollowSet(access(name, mode), S0),
FollowSet(access(name, mode), S0) (access()||$open())*;(open(name1,flags,mode1))
This is a new state, we insert it into states_set, call it S1.
The edge t here is (S0, access(name, mode), S1);
All the computations about the first event access(name, mode) are done.
For the second event $open(f, f1, mode) in the events_set, we add its variables into FSM BindingSet(FSM, $open(f, flag, mode) ) FSM is: struct FSM {
CString access_name; mode_t access_mode;
CString $open_f; int $open_flag; mode_t $open_mode;
We gets next state S1 by computing FollowSet($open(f, flag, mode, S0)
This is a new state , we insert it into states_set, call it S2.
The edge t here is (S0, $open(f, flag, mode) S2);
All the computations about the second event $open(f, flag, mode) are done.
All the computations of S0 are finished here We repeat the steps on state S1, S2, and get the following results:
The FirstSet of S1 is:{access(), $open(), open(name1, flags, mode1) }
BindingSet(FSM, open(name1, flag) ) FSM is: struct FSM {
CString access_name; mode_t access_mode;
CString $open_f; int $open_flag; mode_t $open_mode;
CString open_name1; int open_flags; mode_t open_mode1;}
Edge (S1, access(), S1) is inserted into edges_set;
Edge (S1, $open(), S1) is inserted into edges_set;
FollowSet(S1, open(name1, flags, mode1) ) = S2;
Edge (S1, open(name1,flags, mode1), S2) is inserted into edges_set;
The FirstSet of S2 is NULL;
There is no more new state created, algorithm terminates The NEFSA is shown as Figure 8.
Code Generation
During code generation, the EFSA created from ASL specifications is transformed into a C++ class, with each ASL specification resulting in a distinct class Each class features a member function for every event, matching the number and types of arguments of the corresponding event At runtime, the system call interceptor sends events to the system call detection engine, which subsequently calls the appropriate member function for each event.
At runtime, a list of active Event-Driven Finite State Automata (EFSA) instances is generated When an event occurs, we evaluate each EFSA instance in the list and execute a state transition based on its current state and the newly received event If an EFSA instance lacks a valid transition, it is considered "terminated."
Figure 8 The constructed NEFSA from Code example 4
$open() open(name1, flags,mode1)
$open(f,f1,mode) access(name,mode)
Examples of ASL Specifications Translated into C++ Class Definitions
This section presents C++ code generated from the compiled specifications, specifically referencing Code Example 3 in section 3.6 The focus is on a cat specification designed to restrict the program's ability to access the password file.
The generated code intercepts all system calls made by the 'cat' command Specifically, for open() calls, it verifies the name of the file being accessed.
/etc/passwd, it simulates a failure of the open() For other system calls, the program allows them to go through without any change
Code Example 7 Preventing cat Program to Read /etc/passwd File: class MonitorProg:
{ private: struct FSM { int cs_;
FSM* next_; int Rule_0_f_; void clone(FSM* p2) {
FSM* start=new FSM start->cs_=0; start->next_=NULL; stack1_=start;
}; int open_entry (CString pathname, int flags, mode_t mode){
FSM* tmp=stack1_; while(tmp!=NULL) {//perform variable binding tmp->Rule_0_f_=pathname; tmp=tmp->next_;
} while (stack1_!=NULL) {//simulate the NEFSA
FSM* cp=stack1_; stack1_=stack1_->next_; switch (cp->cs_) { case 0:{ if( realpath(cp->Rule_0_f_)=="/etc/passwd") { cp->cs_=1; fakedRC(-1); cp->next_=stack2_; stack2_=cp;
} break; } default: { delete cp;break;}
FSM* start=new FSM; // always put a initial state start->cs_=0; start->next_=stack2_; stack2_=start; stack1_=stack2_;
As the second example, we consider the race condition vulnerability described in section 3.6 Code Example 4 The code is shown on Appendix B.
SUMMARY AND CONCLUSION
Effectiveness
This section evaluates the effectiveness of our specification-based technique in detecting various known computer intrusions, specifically focusing on UNIX machines We analyzed advisories from the CERT Coordination Center covering a five-year period from 1993 to 1997 The findings of our study are summarized in Table 1.
Trojan Horses in privileged programs 3 1 33
Design weaknesses in protocols, authentication, encryption etc.
During the analyzed period, CERT issued over 110 advisories, with approximately 9 cases lacking identifiable vulnerabilities The remaining advisories either pertain to non-UNIX systems, have been superseded, or reiterate previously mentioned vulnerabilities, leaving us with 82 relevant incidents These were categorized into four groups, with the first three primarily involving vulnerabilities outside the scope of our technique, such as design flaws, weaknesses in encryption, and misconfigurations, which typically do not alter process behavior and are challenging to detect However, unusual behaviors can be identified with suitable ASL specifications The final category focuses on vulnerabilities that exploit program flaws, where our technique excels, effectively capturing about 90% of intrusions, most of which can be mitigated through appropriate ASL specifications.
Summary
This thesis presents a novel approach to intrusion detection by utilizing high-level specifications to define the expected security-related behavior of processes By capturing the normal or intended behavior, any deviations from these specifications serve as indicators of potential intrusions, enabling the detection of attacks that have not been previously encountered Unlike prior research that primarily focused on specifying misuse or intended behavior, our method encompasses the specification of misuse, intended behavior, and the appropriate responses to intrusion attempts, thereby enhancing overall security measures.
Damage from an attack occurs through system calls made by the compromised process to the operating system File manipulation and network connection operations are controlled via these system calls, allowing us to represent security-related behavior in terms of the calls made by each active process By intercepting and validating system calls in real-time, we can identify deviations from expected behavior, enabling us to detect potential issues before they lead to damage, thus providing a preventive security measure.
We developed the Auditing Specification Language (ASL), a high-level language designed to define security-related behaviors and integrity constraints over time ASL allows users to create specifications that are then compiled into optimized C++ programs, enabling efficient detection of deviations By focusing on the relationships and constraints necessary for a correct system, ASL simplifies the specification process without requiring users to delve into the verification details, thereby enhancing usability.
Our compiler converts ASL specifications into an Extended Finite-State Automaton (EFSA), which enhances the traditional finite-state automaton by incorporating a set of state variables This EFSA can be simulated in real-time, allowing for efficient intrusion detection.
Our ASL specification introduces the concept of atomic execution to address vulnerabilities arising from race conditions and interference among multiple processes This feature enables users to articulate the intended behavior of programs that experience synchronization issues or concurrent access errors, which would typically require identification through established patterns of misuse.
Conclusion and Future Work
To establish a reliable intrusion detection system, it's essential to understand that all intrusions ultimately affect the system through system calls, which act as the interface between application software and the OS kernel A critical element of our approach is the use of a specification language, ASL, designed to outline the patterns of system calls that need to be monitored Developing an efficient EFSA is vital for enhancing overall system performance, focusing on reducing both pattern matching time and the automaton's size Additionally, atomic execution ensures data integrity within a multi-process system.
We have successfully completed the ASL design and developed a parser that translates the ASL specification into an EFSA, which in turn generates C++ code While the atomic execution design is finalized, its implementation is still pending Additionally, we aim to create a straightforward characterization of ASL patterns for compilation into DEFSA, as DEFSA offers greater efficiency compared to the current NEFSA.
Currently, the task of writing specifications is solely handled by experienced administrators To improve this process, we can leverage insights from misuse and abnormal intrusion detection systems to create an expert system that assists users in drafting specifications Additionally, developing a graphical interface will enable users to visually observe how their specifications transform into automata, enhancing understanding and usability.
APPENDIX A CLASSIFICATION OF SYSTEM CALLS IN RED
This article provides a comprehensive overview of event abstracts in Red Hat Linux, categorizing system calls into eight main categories with various subcategories Each subcategory features a detailed list of abstract events accompanied by their corresponding original system calls, ensuring clarity and organization for readers seeking to understand the system's functionalities.
WriteOpen (path) - open and possibly create a file for write = { open(path, flags) | (flags & (O_WRONLY | O_APPEND | O_TRUNC)), open(path, flags, mode) | (flags & (O_WRONLY | O_APPEND | O_TRUNC)), creat(path, mode);
ReadOpen(path) - open a file for read = { open(path,flags) | (flags & O_RONLY), open(path,flags,mode) | (flags & O_RONLY),
Open_all(path) - open a file = {
} truncate_2(path, len) - truncate a file to a specified length = { truncate(path, len), ftruncate(fd, len) | path = fdToName(fd)
The `creat` function is used to create a file, with the syntax `int creat(const char *pathname, mode_t mode);` In contrast, the `open` function can open an existing file or create a new one, utilizing the syntax `int open(const char *pathname, int flags);` or `int open(const char *pathname, int flags, mode_t mode);` To truncate a file to a specific length, the `truncate` function is employed with `int truncate(const char *path, size_t length);`, while `ftruncate` is used with `int ftruncate(int fd, size_t length);` for file descriptors Lastly, the `pipe` function creates a pipe with the syntax `int pipe(int filedes[2]);`.
File Attributes filePermCheck(path) - check file permission = { stat(path,buf) fstat(fd, buf) | path = fdToName(fd) lstat(path, buf) access(path, mode)
} fileAttrCheck(path) - check any file attribute = { stat(path, buf) fstat(fd, buf) | path = fdToName(fd) lstat(path, buf)
} filePermChange(path) - change file permissions = { chmod_2(path, mode) chown_2(path, owner, group)
} fileAttrChange(path) - change file attributes = { filePermChange(path) stat_2(path, buf) - get file status = { stat(path, buf) fstat(fd, buf) | path = fdToName(fd)
} chmod_2(path, mode) - change permissions of a file = { chmod(path, mode), fchmod(fd, mode) | path = fdToName(fd);
} chown_2(path, owner, group) - change ownership of a file = { chown(path, owner, group), fchown(fd, owner, group) | path = fdToName(fd)
} link_2 (oldpath, newpath) - make a new name for a file = { link(oldpath, newpath), symlink(topath, frompath)
The `access` function checks a user's permissions for a specified file, while `stat`, `fstat`, and `lstat` retrieve the status of a file To set a file creation mask, the `umask` function is utilized The `utime` and `utimes` functions modify the access and modification times of an inode For changing file permissions, `chmod` and `fchmod` are used, whereas `chown` and `fchown` change the ownership of a file The `link` and `symlink` functions create new names for files, and the `rename` function alters the name or location of a file Finally, the `unlink` function deletes a file or its reference.
The functions `llseek` and `lseek` are used to reposition the read/write file offset in a file descriptor The `_llseek` function takes parameters including an unsigned integer for the file descriptor, two unsigned long integers for the high and low parts of the offset, a pointer to a `loff_t` variable to store the result, and an unsigned integer to specify the position from which to offset Similarly, the `lseek` function requires an integer for the file descriptor, an `off_t` for the offset, and an integer to indicate the reference point for the offset adjustment Both functions are essential for manipulating file pointers in a variety of applications.
_llseek(fd, offset_high, offset_low, * , whence) | offset = (offset_high