Hi, I’m crooked.
Log4j has a bug, and a few days ago a friend asked me: Will it matter if I use Lombok’s @slf4j in my project?
What a coincidence that is, I also used this note, so I looked at it a little bit.
Conclusion: It depends on the Log4j2 package you rely on for your project, and has nothing to do with Lombok.
And the words “Please think before you ask, don’t waste our time, don’t ask questions you can figure out yourself” are not from me, but from the author of Lombok:
Why did he say something with a tinge of anger like that?
Let me show you.
From the issue
You can find the Lombok project on Github and check its issue to see that the log4j issue has been topped:
So for this question, I’ll start with the issue, which has an authoritative answer from Lombok’s maintainer.
Github.com/projectlomb…
The title of the issue translates as:
Conclusion: Lombok itself is not affected by log4j’s 0day problem.
Neil: Notice that there is a very interesting word in this one: itself.
Lombok itself is usually said to be unaffected. Lombok itself is usually said to be unaffected.
There’s a story to it.
First, at 5:41pm on December 10, the afternoon the bug was reported, a guy on Lombok’s Issue posed this question:
Log4j has a huge bug. Fix it.
And then this old iron man named Rawi01 jumped in and said,
Dude, can you explain how this bug affects Lombok? As far as I know, Log4J is only needed to run tests without compile errors.
This guy obviously knows a lot about Lombok, but he’s already pointed out the key point: Lombok doesn’t rely on Log4j.
But then someone pointed out:
Lombok provides an annotation called @log4j. It uses the Log4J library, after all, for logging.
There is indeed this note, let me show you:
I usually use the @slf4j annotation, but it does provide other logging annotations, which I’ll leave off later.
Now the main task is this issue, and read on.
Next, here comes a guy who controls the court, and here’s what he says:
He called the @log4j annotation and explained it to him: Although Lombok generates code to create an instance of Logger, we do not ship, Distribute or require any particular version, and users need to provide their own dependencies if they want to use this annotation.
I have three words here that I haven’t translated: ship, distribute or require.
Require is simply a requirement.
This means that we do not require the user to use the specified version of the dependency. In plain English, Lombok doesn’t care what version of a dependency you provide, assuming you want to use @log4j.
And the rest of the word ship literally means “deliver,” and distribute.
Let me illustrate this with a picture:
As you can see, I referenced Lombok in the project, but it didn’t pass dependencies in to any other packages. It’s not like spring-boot-starter followed by a bunch of shit.
I understand that this is what he meant: We do not ship, distribute or require any specific version.
Then I continued: we do find a version of log4j2 in our code base, but that is only used in test code so that the generated code can be compiled error-free.
Lombok is still safe to use, he concludes. But I feel that even though Log4j is referenced in the test code, I should update the dependencies to a safer version.
What do you think? It feels like this guy’s got a certain unquestionable confidence about him.
That’s what I felt when I read this answer. I vaguely felt like I was a big man.
So I went to the background of this brother:
I was looking for an answer on his Github page, but there was no Lombok-related project.
And only 49 followers, it doesn’t fit the big guy’s data:
So I went to the browser and searched for: Roel Spilker Lombok.
Found this:
Oh, boy, he’s Lombok’s dad. No wonder he’s so tough.
Another tidbit: they had previously pitched Lombok to Oracle, but had been turned down. The authorities didn’t support it. They had to rise up and do it themselves.
Lombok still has a lot of market share, so it worked.
In addition, you can directly see his identity:
Collaborator. His answer is equivalent to the official one.
So far, things have been developing smoothly. The official personally came to the court to answer this question, and it has been clearly stated:
That’s the end of it.
But, you know, I was afraid to show up but, uh, what happened next seemed a little crazy to me.
First up, SunriseChair took the kill:
He first quotes the author’s response and then says, “If you declare Lombok dependencies in your Maven or Gradle, wouldn’t Log4j dependencies be included? This is probably the only dependency on the CLASspath that Lombok-generated code uses, right? Please correct me if I am wrong……
What does he mean by that?
First of all, I think he read the author’s answer and decided Lombok relies on Log4j, so his core question is, if I reference Lombok, won’t Log4j’s dependency pass through?
There is a version to be found in our code base, but that’s only for testing classes.
If you know anything about how open source projects work, you know that the test parts are not provided. If you want to see the test classes, you have to pull down the project source code.
The short answer is that if you rely on Lombok through Maven, you won’t see anything about testing.
Then a brother named RuanNunes came out and did a double, and he said he searched through the whole project and found this:
This configuration is flawed.
Then the author responded to these two questions one by one:
First, Lombok relies on Log4J. Listen: Lombok doesn’t rely on Log4j or any other library. If you use @log4j annotations in your code, but do not rely directly or indirectly on Log4j, your compilation will generate an error message.
The dependency analysis screenshot above illustrates this point and I won’t post it again.
Listen: there is a text file in the same directory as the bug you found. If you look at it, you will know that we will not use this file as part of our distribution.
So I understand that it will not be released at all, and even if it is buggy, it will not be a problem for Lombok users?
I also looked up the file the author was talking about, and here it is:
Github.com/projectlomb…
Lombok itself not part of lombok itself.
If the previous two questions have made the author feel a little uncomfortable, then the next question may be the tipping point, complete the triple kill, a wave away:
The elder brothers come up and say: old iron, I ask a question. I am using @slf4J annotation. Does this bug matter to me?
No, no, no. Blood pressure’s up.
I think this guy didn’t read the previous replies before he asked the question. If he asked the question before the author explained it twice, then I think it’s totally understandable.
But after the author has already explained twice in this issue that Lombok is not affected, he bam, a crit:
Dude, is there something wrong with this note I’m using?
I’m starting to feel a little blood pressure as I write this.
Before this, yao particularly like once in a group and others to discuss the transaction failure, has been discussed in the end, suddenly jump out to a the elder brothers, shrugged off a code fragment, the fragment of writing is a classic of this call is leading to the invalid transaction annotations, and this is part of the we just discussed.
You know we’re talking about this, even if you scroll up a little bit, you know the context before you ask?
Without digress, maybe I’m reading too much into it, but I think the art of asking questions needs to be learned.
Back to our main story, take a look at the author’s answer to this question:
He said, Old tie, I can’t help you with this problem. I don’t know anything about this bug and I don’t even understand why you think it affects @slf4j as well.
What I can tell you is that Lombok does not use, pass, or require dependencies on these libraries.
The way we work is by generating your invisible source code.
If you have any reason to suspect that similar problems might occur in their products, please contact the maintainers of Slf4j.
I don’t know if it’s my personal feeling, but I think the author responded to this question with a bit of anger: What kind of questions are these? I have replied twice before not affected ah? How does it feel that my project users don’t understand Lombok’s fundamentals?
After the author replied to the cheeky questioner, the questioner politely replied:
Thanks for checking, I’m using the default SpringBoot logback.
The author also put this issue at the top and changed the title of the issue.
So, after the previous reading, now you look at this headline:
Lombok does provide logging, but it has nothing to do with which package you refer to, or which version of a package. Lombok itself is safe.
Finally, log4j vulnerability for Lombok’s Latest assesment is given as a summary:
Translate the key things for you.
This vulnerability only exists in the Log4j code package up to version 2.16.0 and not in any other logging framework.
Lombok does not pass dependencies on any Log4j package, nor does it declare dependencies on anything.
If you use any Lombok annotations, such as @log4j, Lombok will generate code that uses these libraries, but your project must include dependencies on these libraries, otherwise Lombok’s generated code will not compile.
Again, you are responsible for having these packages in your runtime, or class initialization may fail.
In Lombok test code, we had a version that included this vulnerability, but running the test did not result in RCE(remote code/command execution vulnerability) on the machine where the test was executed because the test did not process any user input (the test was hard-coded) and the generated code was not even executed.
So Lombok itself doesn’t need to change anything, and it’s not responsible for the security of your project, because we didn’t import the package.
If you disagree with the current assessment, please add a comment on this issue.
However, please make sure you have read the other comments and make sure you understand the question.
These last two sentences, on their own, are my favorite:
Please think before you ask questions, don’t waste our time, don’t ask questions that you can figure out for yourself.
If you think we missed something, or have new information, please speak up.
Then, notice The subtitle The author uses here: The Balancing Act.
It translates as “balancing act.” What the hell?
NO, NO, NO
A little slang for everyone, make yourself at home.
added
The main plot has been finished, now I would like to add a few notes.
First of all, press the unlisted notes about logging.
There are quite a few notes on logging in Lombok, as you can see from the official documentation:
Projectlombok.org/features/lo…
So many annotations, one by one is not interesting, I will pick @slf4j, @log4j2 these two demonstration.
First, we could have a clean SpringBoot project with only these two dependencies:
If I do nothing at this point and just change the startup class slightly:
Then I changed the log printing level to Error in order to eliminate interference items:
logging.level.root=error
Also turn off the banner output:
spring.main.banner-mode=off
The banner is this thing:
When the project is started, the log output looks like this:
As you can see, we are using logback for the reason I mentioned in the previous article, because Springboot uses logback as the default logging implementation.
This can also be seen from project dependencies:
Also, notice that I have deliberately cut out the import section and have not introduced any logging annotations other than the @slf4J annotations.
Then, I’ll look at the class file compiled at this point:
Slf4j-related packages are automatically introduced, and this line of code is generated:
private static final Logger log = LoggerFactory.getLogger(LogdemoApplication.class);
I don’t know if you’ve thought of compile-time annotations at this point, but don’t panic, I’m going to go ahead and click on them.
I ask you: why can it introduce slf4J related packages?
Because I am dependent:
Okay, so if I take the logback core dependency away at this point, what’s going to happen, do you think it’s going to compile?
It will not compile, however, because the Slf4j package is still there, it is just a logging facade.
An exception will be thrown at runtime because no specific logging implementation class can be found:
Then, what if I want to use the log4j2 logging implementation?
As I wrote in my previous post:
Remove the Springboot default dependencies and introduce the Log4j2 package.
The project dependency diagram now looks like this. You can see that there is no logback-core, only log4J-core:
Run the project again, and the logging implementation becomes Log4j:
Did you notice that I changed the logging framework from LogBack to Log4j without touching a single line of code other than the POM dependency?
And the class file doesn’t change anything, so I won’t take screenshots.
This is where Slf4j comes in, this is what “facade” means, and this is why it is recommended that you use Slf4j in your projects rather than specific logging implementations such as Logback and log4j.
Now let’s talk about this annotation at sign Log4j2.
Let’s restore the dependency to its pristine state, which looks like this:
Then we changed the annotation to @log4j2, but we didn’t introduce the log4J-Core package at this point in the project, so do you think that would be a problem?
It won’t be a problem. We can take a look.
Take a look at the output:
The logging implementation class at this point is SLF4JLogger.
Where did you get this thing?
Take a look at the class file:
The two classes are from the log4J-API package, and due to the log4j-to-slf4j package, the final implementation class Bridges to the SLF4JLogger:
If I remove the log4J-API package, do you think it will compile but?
The package does not exist, and the class file cannot be created:
If I don’t want to use the SLF4JLogger class, I want to use the real log4j.
Simple, include log4j’s dependencies:
Ok, so I’ve gone out of my way to exclude logging packages, introduce logging packages, and show you what the output looks like, and the whole process doesn’t involve changes to Lombok packages, just to reconfirm these two statements:
If you use any Lombok annotations, such as @log4j, Lombok will generate code that uses these libraries, but your project must include dependencies on these libraries, otherwise Lombok’s generated code will not compile.
For example, if I removed the log4J-API package, did I not compile it?
Again, you are responsible for having these packages in your runtime, or class initialization may fail.
For example, if I removed the logback-core package, I could compile it without any problems, but when the service runs, I could not find the class exception.
Does it prove again:
Talk about the principle of
Earlier I mentioned “compile-time annotations”, I don’t know if you are familiar with this stuff.
This is not surprising, because we rarely define compile-time annotations when we write business code. At best, a run-time annotation will do the job.
At the heart of Lombok’s work is compile-time annotations.
In fact, MY understanding is not in-depth, just roughly know how it works, there is no in-depth study of the source code.
But I can share with you two things to look out for and where to go to learn about it.
The first thing to notice is here:
The source code for the log annotations is located in this section, and you can see that it is strange that these files end with SCL. Lombok. What is this?
Lombok’s trick is that these are actually class files, but it makes them special to avoid contaminating user projects.
So when you open a file like this, just open it as a class file, and you can see what’s inside.
For example, you can look at this file:
lombok.core.handlers.LoggingFramework
You’ll find that you write a lot of logging implementations like enumerations:
This one hardcodes the log that needs to be generated for each annotation. Because of this, Lombok knows what log annotations you use and what logs should be generated for you.
For example, log4j looks like this:
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class);
This also corresponds to our previous class file:
SLF4J looks like this:
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class);
The second thing to watch out for is finding the entrance:
The entry point for loading these class files is here, based on the JAVa-based SPI mechanism:
AnnotationProcessorHider class contains two static inner classes, one of which is AnnotationProcessor, an abstract class derived from AbstractProcessor:
javax.annotation.processing.AbstractProcessor
This abstract class is the entry within the entry, the core within the core.
In this entry, we initialize a classloader called ShadowClassLoader:
All it does is load the class files labeled scl.Lombok.
Then how do I know Lombok is based on compile-time annotations?
In fact, this thing is written in the two books I have read, a little vague impression, when writing the article I turned out to read again.
The first is chapter 10: Front-end Compilation and Optimization, part 4 of Understanding the Java Virtual Machine (3rd edition).
There’s a section about parenthetical annotations:
Lombok’s main work place is in the process of compiling javac.
On page 361, several stages of the compilation process are mentioned.
From the overall structure of Java code, the compilation process can be roughly divided into a preparation process and three processing processes:
-
1. Preparation: Initialize the plug-in annotation handler.
-
2. Process of parsing and filling symbol table, including:
- Lexical and grammatical analysis. An abstract syntax tree is constructed by converting the character stream of the source code into a tag set.
- Populate the symbol table. Generates symbolic addresses and symbolic information.
-
3. Annotation processing by the Plug-in annotation processor: During the execution phase of the plug-in annotation processor, the actual section of this chapter designs an plug-in annotation processor to influence the compilation behavior of the Javac.
-
4. Analysis and bytecode generation process, including:
- Label check. Check for static information about the syntax.
- Data flow and control flow analysis. Check the dynamic running process of the program.
- Solution sugar. Return syntactic sugar that simplifies code writing to its original form. (Syntax sugars in Java include generics, varied-length parameters, auto-unboxing, foreach traversal loops, and so on, which are not supported by the JVM runtime and need to be restored at compile time.)
- Bytecode generation. Convert the information generated by the previous steps into bytecode.
If javac compilation is Lombok’s workspace, then “annotation processing for the plug-in annotation processor” is its workspace.
The book also explains how Lombok works:
Chapter 8 of the second book, Understanding JVM Bytecode in Depth, also provides a detailed description of how plug-in annotations work, including a reference to Lombok:
Finally, I drew a schematic diagram like this:
If you understand the descriptions in the first dozen or so pages of the book, this diagram will be easier to read.
In short, the core principle of Lombok is that you can create a lot of code by tinkering with class files at compile time.
This is also what the author mentions:
Invisible Source Code, invisible source code.
By invisible, I mean invisible in Java files, and it’s still invisible in class files.
If you’re interested in learning more about how it works, check out the two books I mentioned earlier, both of which have hands-on development.
I’m not going to write it, partly because the bar is really high and it’s hard to understand. The other reason is because I’m lazy.
This article has been included in personal blog, welcome to play:
www.whywhy.vip/